Mysql常用引擎InnoDB支持行锁、表锁,而MyISAM则不支持
以下均默认InnoDB
以下是MySql锁类型
共享排他锁
从是否共享来看可以将锁分为共享、排他锁。
- 不同事务可以对同一行记录加共享锁
- 若一事务对某一行加共享锁,其他事务请求排他锁不能被立即允许
- 若一事务对某一行加排他锁,那么其他事务就不能加共享或者排他锁,从而导致锁等待
可以总结共享、排他锁兼容关系如下图所示
InnoDB中,只能给索引上的索引项加锁来实现,也就是说对于非索引项加锁,将会使用表锁。
意向锁
另外为了让行级锁定和表级锁定共存,也使用了意向概念。就是当一个事务在需要获取资源锁定的时候,如果碰到自己需要的资源已经被排他锁占用,可以在锁定行的表上添加意向共享锁或者意向排他锁。
比如事务A修改user表的记录r,会给记录r上一把行级的排他锁(X),同时会给user表上一把意向排他锁(IX),这时事务B要给user表上一个表级的排他锁就会被阻塞。意向锁通过这种方式实现了行锁和表锁共存且满足事务隔离性的要求。
意向共享锁(IS)表示事务意图在表行设置共享锁
意向排他锁(IX)表示事务意图在表行设置排他锁
# Intention Shared lock
select * from t for share;
# Intention Exclusive lock
select * from t for update;
意图锁协议如下
- 在事务可以获取共享锁时,一定是第一个获取共享以及更强大的锁的
- 在事务可以获取排他锁时,一定是第一个获取排他锁
加入意向锁维度之后,兼容关系如下
X | IX | S | IS | |
---|---|---|---|---|
X | Conflict | Conflict | Conflict | Conflict |
IX | Conflict | Compatible | Conflict | Compatible |
S | Conflict | Conflict | Compatible | Compatible |
IS | Conflict | Compatible | Compatible | Compatible |
可知排他锁对所有其他锁都冲突,意向锁之间相互兼容,共享锁对共享锁以及意向共享锁兼容。
意图锁除了会堵塞全表扫描之外(lock tables),其他的都不会堵塞
可能看到这个意向锁的时候会有一个疑问,为什么意向锁是表锁呢?
这是因为我们想要给表加排他锁的时候,如果意向锁是行锁,那可能就会要一次次的遍历。
间隙锁
间隙锁即锁住索引间隙以防止插入,或者首尾间隙。(开区间也就是不包括双端节点,并且不能更改其他行id为间隙内的id)
对于非索引且非为唯一索引的列加锁,则会导致整个表被锁,无法进行写。
select * from t where name='demo'
间隙锁可以用于防止幻读,保证索引之间不会插入值
# 锁住46到56之间的值,不包括46、56
select * from group_member where id>46 and id <56 for update ;
# 能更新边界46
update group_member set name='hello' where id=46;
# 可是将46改为47就触犯锁区间
update group_member set id=47 where id=46;
# 设边界为1与10
# 当update 为1到10之间不存在的行时,会锁住整个间隙
update t set name = 'hello' where id =9
Next-Key 锁
next-key锁是行锁和以及该行索引之前间隙的结合。也就是闭区间
# # 锁住10与20之间索引(包括10与20)。比如当另一个事务想要插入15的时候,可能会被拒绝,无论是不是有15这个索引
# 当然10和20也可以根本不存在,仍然能锁住
select c1 from t where c1 between 10 and 20 for update;
insert into t (c1) values(15)
插入意向锁
插入意向锁是一种在插入过程中产生的间隙锁,而不是意向锁
插入时会对插入的行加行锁,插入意向锁不会阻止任何锁
主要是为了提升并发插入能力,能够允许在范围内插入数据
注1:兼容性和加锁顺序有关系,因此兼容性表格不是对称的
注2:此表格阅读方式为先列后行;
是否兼容 | gap | insert intention | record | next-key |
---|---|---|---|---|
gap | 是 | 是 | 是 | 是 |
insert intention | 否 | 是 | 是 | 否 |
record | 是 | 是 | 否 | 否 |
next-key | 是 | 是 | 否 | 否 |
从图中可以看出若前一个事务持有gap锁、next-key锁的时候,后一个锁想要持有插入意向锁的时候会不兼容,出现锁等待。
下表索引47周围只有46与56
T1 | T2 | |
---|---|---|
insert into id=47 | ||
insert into id=46(会报错Duplicate entry) | ||
update set id=50 where id =46(OK) | ||
insert into id=47(会锁住) | ||
insert into id=50(OK) | ||
insert into id=45(OK) |
注意:插入意向锁和行锁区别在于意向是之前没有该索引,而行锁则是已经有该索引
自增锁
自增锁是表锁,当有自增列出现,会对整个表加auto_inc锁,阻止其他事务的插入操作,保证生成的自增值肯定是唯一的
- auto_inc互相不兼容
- 自增值一旦分配就会+1,即使发生回滚,也不会减回去
会对整个表加锁是在traditional的情况下,还有consecutive以及interleaved两种情况(在Mysql8.0之前默认为consecutive模式,之后为interleaved)
- traditional:自增时会锁住整个表,对于多事务并发插入,性能下降
- consecutive:提前获取数量的insert语句,可以通过mutex更为轻量级锁来防止重复分配。当然对于insert into … select 这种未知数据量还是表锁
- interleaved:所有插入语句都使用轻量级mutex锁。副作用就是自增值并不会连续
死锁
会话都持有gap锁情况下,去申请插入意向锁
周围只有46,56索引
T1 | T2 |
---|---|
update id=47 | |
会在46、56区间内加间隙锁 | |
update id=48 | |
因为间隙锁兼容,所以也能申请46、56区间内的间隙锁 | |
insert id=49 | |
由于插入意向锁与T2的间隙锁不兼容,所以会发生等待 | |
insert id=50 | |
此时也会也T1间隙锁不兼容,并且最终导致死锁 |
申请加锁排队
-- 数据如下(uid为唯一索引,id为主键)
id u_id d_id
25 12 16
T1 | T2 |
---|---|
select * from user_department where id=25 for update | |
为主键=25加锁 | |
update user_department set depart_id=12 where user_id=12 | |
为索引=12加锁,并且等待给主键加锁。如果此时select id=25 for update,之后也不会发生死锁,因为T2在这种情况不会持有任何锁 | |
delete from user_department where id=25 | |
delete看上去只是给id=25加锁,可是当换成update操作的时候,并没有发生死锁;而换成update u_id=12却发生了死锁,由此可知delete可能也申请加u_id=12的索引锁 |
主键与普通索引加锁区别
主键:
- 为表添加意向锁
- 等值存在,对该主键索引加行排他锁
- 等值不存在,在主键加前开后开间隙锁
普通索引:
-
等值存在,for update在本索引以及主键加锁,for share在走覆盖索引,会仅给自己的索引加锁
-
等值不存在,在本索引加前开后开间隙锁
插入冲突,添加next-key
-- 数据如下(uid为唯一索引,id为主键)
id u_id d_id
1 1 1
5 4 5
20 20 1
25 12 16
T1 | T2 |
---|---|
insert into user_department(id, user_id) VALUES (26,10) | |
按理来说,应该是id=26的插入意向锁和u_id=10的插入意向锁 | |
insert into user_department(id, user_id,) VALUES (30,10) | |
id=30的插入意向锁,但是在u_id=10的插入意向锁起了冲突,因为T1已经锁住了这行了。也就是会发生等待,并且在冲突unique key添加共享的next-key 锁 | |
insert into user_department(id, user_id) VALUES (40,9) | |
由于T2已有了next-key锁,所以会导致此时的插入意向锁无法成功加锁,并且又因为T2又在等待T1 u_id=10的行锁,最后便造成了死锁 |
Ref
- https://www.cnblogs.com/jian0110/p/12721924.html
- https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html
- https://blog.csdn.net/u010841296/article/details/84204701
- https://z.itpub.net/article/detail/7B944ED17C0084CF672A47D6E938B750
- https://segmentfault.com/a/1190000040139810?utm_source=sf-similar-article
- https://juejin.cn/post/6878884451162521613#heading-30
- https://juejin.cn/post/6968420054287253540