mysql中也支持各种锁–今天来将脑海中的知识来整理一下(听说大厂面试可能会问到mysql中锁的机制问题,所以还是好好学习吧)
就像java中各种锁 轻量锁,偏向锁,重级锁–目前我还不太熟悉-------- 慢慢学习吧,我刚入门要学习的东西很多–谁让我踏上了程序员之路
首先按照表来区分锁
innodb支持表锁,行锁;
myisam支持表锁;
dbd支持页面锁和表锁;
表锁加锁快,开销小,不会出现死锁;锁定范围大,发生锁冲突的概率最高,并发度也是最低;
行锁加锁慢,开销大,会出现死锁;锁定范围小,发生锁冲突概率小,并发度高;
页面锁是介于表锁和行锁之间,范围介于两者之间,并发度一般;
由于mysql8.0默认引擎innodb,默认为行锁,所以先整理一下行锁----
innodb引擎中的行锁
行锁–
行锁分为共享锁和排他锁
共享锁---->又叫读锁,当一个线程获取读锁后,会阻塞其他用户对该行数据的写操作(即阻止其他事务的排他锁),但不会阻止其他用户对改行数据的读操作(共享锁);
排他锁---->又叫写锁,当一个线程获取写锁之后,会阻塞其他用户对改行数据的读写操作
行锁的实现方式—
InnoDB行锁是给索引项加锁来实现的。所以只有通过索引项来操作数据才会有行锁,如果没有操作索引项 用的则是表锁;
默认情况下innodb用的是隐式加锁–
对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加写锁–排他锁(X)因为要操作数据;
对于普通SELECT语句,InnoDB不会加任何锁,因为不需要操作数据,只是读取数据;
显示加锁的操作格式如下–
共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
排他锁(X) :SELECT * FROM table_name WHERE ... FOR UPDATE
1
2
对于行锁用在事务的操作上,单纯为某一行数据加行锁没有太大意义(行锁是加在索引上的而不是数据上的)下面做一下演示:
mysql> start transaction ;
Query OK, 0 rows affected (0.00 sec)
mysql> desc huixin ;
+--------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-------------+------+-----+---------+-------+
| user_id | int | NO | PRI | NULL | |
| user_name | varchar(64) | YES | | NULL | |
| basic_salary | int | YES | | NULL | |
+--------------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
mysql> select user_id ,user_name from huixin where user_id=1002 for update ;
+---------+-----------+
| user_id | user_name |
+---------+-----------+
| 1002 | 赵四 |
+---------+-----------+
1 row in set (0.00 sec)
mysql> commit ;
Query OK, 0 rows affected (0.00 sec)
mysql> start transaction ;
Query OK, 0 rows affected (0.00 sec)
mysql> select user_id ,user_name from huixin where user_id=1002 lock in share mode ;
+---------+-----------+
| user_id | user_name |
+---------+-----------+
| 1002 | 赵四 |
+---------+-----------+
1 row in set (0.00 sec)
mysql> insert into huixin values(1008,'李二狗',4500);
Query OK, 1 row affected (0.00 sec)
mysql> update huixin set user_name='zhaosi' where user_id=1002 ;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
用select 。。。。。where [condition] lock in share mode 来获得共享锁,在查询时候确保该记录没有被其他用户用update或者delete所操作,不然查询的数据可能跟原表不能保持一致。
在这里插入图片描述以上是两个用户登陆数据库,由于不能同时触发命令所以也就无法触发错误提示,仅供了解,如果是不同的设备登陆,如果我在查询的时候加了行级锁,另一个设备2同时在修改我要查询的数据,则会阻塞设备2上对该数据的修改;
用select 。。。。。where [condition] lock in share mode 来获得排他锁,在修改数据的时候不允许其他用户读和写该数据;
在这里插入图片描述以上是两个用户登陆数据库,由于不能同时触发命令所以也就无法触发错误提示,仅供了解,如果是不同的设备登陆,如果我在修改数据的时候加了行级锁,另一个设备同时在查看或者修改我要修改的数据,则会阻塞在设备2上对该数据的操作;
以上就是行锁的原理;
下面是表锁—
innodb和myisam以及dbd都支持表锁,所以还是以innodb的表锁为例演示–
mysql> use company
Database changed
mysql> #这里是不上锁的表huixin;
mysql> desc huixin ;
+--------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-------------+------+-----+---------+-------+
| user_id | int | NO | PRI | NULL | |
| user_name | varchar(64) | YES | | NULL | |
| basic_salary | int | YES | | NULL | |
+--------------+-------------+------+-----+---------+-------+
3 rows in set (0.03 sec)
mysql> insert into huixin values(1011,'王二小',3600);
Query OK, 1 row affected (0.01 sec)
mysql> #以下是上表锁的huixin;
mysql> lock table huixin read ;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from huixin ;
+---------+-----------+--------------+
| user_id | user_name | basic_salary |
+---------+-----------+--------------+
| 1001 | 爱德华 | 2500 |
| 1002 | 赵四 | 2500 |
| 1003 | 王伟 | 2200 |
| 1004 | 宫本 | 2100 |
| 1005 | 黄淮 | 3000 |
| 1006 | 胡歌 | 5000 |
| 1007 | 张浩 | 6500 |
| 1008 | 李二狗 | 4500 |
| 1011 | 王二小 | 3600 |
+---------+-----------+--------------+
9 rows in set (0.00 sec)
mysql> insert into huixin values(1009,'郑中基',5620);
ERROR 1099 (HY000): Table 'huixin' was locked with a READ lock and can't be updated
mysql> #上读锁之后,表就处于只读状态,不能修改表中数据;
#解锁
mysql> lock table huixin write ;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into huixin values(1009,'郑中基',5620);
Query OK, 1 row affected (0.00 sec)
mysql> select * from huixin ;
+---------+-----------+--------------+
| user_id | user_name | basic_salary |
+---------+-----------+--------------+
| 1001 | 爱德华 | 2500 |
| 1002 | 赵四 | 2500 |
| 1003 | 王伟 | 2200 |
| 1004 | 宫本 | 2100 |
| 1005 | 黄淮 | 3000 |
| 1006 | 胡歌 | 5000 |
| 1007 | 张浩 | 6500 |
| 1008 | 李二狗 | 4500 |
| 1009 | 郑中基 | 5620 |
| 1011 | 王二小 | 3600 |
+---------+-----------+--------------+
10 rows in set (0.00 sec)
mysql>
innodb间隙锁
话不多说上代码—
#session1中,首先在huixin表中插入一列num,并设置为普通索引,(已经设置好了)这里不演示操作可自行操作
mysql> select * from huixin where user_id>1003 for update ;
+---------+-----------+--------------+-----+
| user_id | user_name | basic_salary | num |
+---------+-----------+--------------+-----+
| 1005 | ceshi | 4556 | 5 |
| 1006 | test | 4455 | 5 |
+---------+-----------+--------------+-----+
2 rows in set (0.00 sec)
mysql> start transaction ;
Query OK, 0 rows affected (0.00 sec)
mysql> begin ;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from huixin where num = 3 for update ;
+---------+-----------+--------------+-----+
| user_id | user_name | basic_salary | num |
+---------+-----------+--------------+-----+
| 1003 | sdsad | 4242 | 3 |
+---------+-----------+--------------+-----+
1 row in set (0.00 sec)
mysql>
#在session2进行操作
mysql> use company
Database changed
mysql> start transaction ;
Query OK, 0 rows affected (0.00 sec)
mysql> begin ;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into huixin values (1234 ,'test',7878,2);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into huixin values (1234 ,'test',7878,4);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into huixin values (1234 ,'test',7878,5);
Query OK, 1 row affected (0.00 sec)
我们可以发现在(1,3]和[3,5)之间上了间隙锁这也是为啥插不进去数据的原因。
总结–设计表的时候尽量避免间隙锁的产生,间隙锁属于行锁的范畴,如果num不属于索引,那么此锁就属于表所的范畴,在huixin表中插入任何数据都会出现阻塞。
死锁
死锁的产生是多个session去操作同一行数据,同时该行数据又被加锁了,演示代码如下–
#session1中
mysql> set@@autocommit =0; #关闭自动提交
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like '%isolation%'; #查看隔离级别
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set, 1 warning (0.00 sec)
mysql> start transaction ;
Query OK, 0 rows affected (0.00 sec)
mysql> begin ;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from huixin where user_id =1111 for update ;
Empty set (0.00 sec)
mysql> insert into huixin values(1111,'ceshi',5200,100);
Query OK, 1 row affected (32.79 sec)
#session2中
mysql> set @@autocommit =0;
Query OK, 0 rows affected (0.00 sec)
mysql> start transaction ;
Query OK, 0 rows affected (0.00 sec)
mysql> begin ;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from huixin where user_id=1111 for update ;
Empty set (0.00 sec)
mysql> insert into huixin values (1111,'ceshi',5200,100);
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
mysql>
可以发现多个session同时操作一行数据(必须是索引才是行锁的范畴),会产生死锁。
死锁的解决方案
首先要解决死锁问题,在程序的设计上,当发现程序有高并发的访问某一个表时,尽量对该表的执行操作串行化,或者锁升级,一次性获取所有的锁资源。
然后也可以设置参数innodb_lock_wait_timeout,超时时间,并且将参数innodb_deadlock_detect 打开,当发现死锁的时候,自动回滚其中的某一个事务。
***总结– ***
innodb存储引擎实现了行级锁,在锁定范围上更小,更精细,能实现更为复杂的操作但是所带来的性能损耗也会比表级锁很大,但是行锁在并发性能上的优势很明显。
当并发量很大的时候,innodb的整体性能要比myisam高,所以在使用何种引擎的时候要考虑锁的问题,在选择锁的时候应该考虑并发量是不是很大,合理使用锁,以下是使用行锁的建议—
尽量控制事务的大小,减少锁定资源量和锁定时长;
数据检索最好是通过索引,因为时间快性能较好(当然数据量小也会进行全表扫描),同时也可以避免升级成表锁–合理使用索引可以更好的提高性能;
尽可能减少在数据范围内的检索条件比如 检索条件 <50 要比 <100要好(这要求我们大致推算出数据的位置),避免间隙锁带来的负面影响而不能操作一些数据;
在合适的情况下尽量使隔离级别较小,同时最好是一次锁定所有的所需操作的资源;
不断学习共同进步!
————————————————
版权声明:本文为CSDN博主「Gavin_Lim」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_54061333/article/details/117840883