MySQL 事务死锁问题排查( 二 )


1. 只会对满足查询目标附近的区间加锁,并不是对搜索路径中的所有区间都加锁 。本例中对搜索 id=5 或者 user_id=5 时,最终可以定位到满足该搜索条件的区域 (1,5] 。
2. 加锁时,会以 Next key Lock 为加锁单位 。那按照 1 满足的区域进行加 Next key Lock 锁(左开右闭),同时因为 id=5 或者 user_id=5 存在,所以该 Next key Lock 会退化为 Record Lock,故只对 id=5 或 user_id=5 这个索引行加锁 。
如果查询的 id 不存在 , 例如:
select * from user
where id = 6 for update
按照上面两条原则 , 首先按照满足查询目标条件附近区域加锁,所以最终会找到的区间为 (5,8] 。因为 id=6 这条记录并不存在,所以 Next key Lock (5, 8] 最终会退化为 Gap Lock,即对索引 (5,8) 加间隙锁 。
2、唯一索引范围查询
select * from user
where id >= 4 and id <8 for update
同理,在范围查询中,会首先匹配左值 id=4,此时会对区间 (1,5] 加 Next key Lock , 因为 id=4 不存在,所以锁退化为 Gap Lock (1,5);接着会往后继续查找 id=8 的记录,直到找到第一个不满足的区间 , 即 Next key Lock (8, 9],因为 8 不在范围内,所以锁退化为 Gap Lock (8, 9) 。故该范围查询最终会锁的区域为 (1, 9)
3、非唯一索引等值查询
对非唯一索引查询时,与上述的加锁方式稍有区别 。除了要对包含查询值区间内加 Next key Lock 之外,还要对不满足查询条件的下一个区间加 Gap Lock,也就是需要加两把锁 。
select * from user
where mobile_num = 6 for update
需要对索引 (3, 6] 加 Next key Lock,因为此时是非唯一索引,那么也就有可能有多个 6 存在,所以此时不会退化为 Record Lock;此外还要对不满足该查询条件的下一个区间加 Gap Lock,也就是对索引 (6,7) 加锁 。故总体来看,对索引加了 (3,6] Next key Lock 和 (6, 7) Gap Lock 。
若非唯一索引不命中时,如下:
select * from user
where mobile_num = 8 for update
那么需要对索引 (7, 9] 加 Next key Lock,又因为 8 不存在,所以锁退化为 Gap Lock (7, 9)
4、非唯一索引范围查询
select * from user
where mobile_num >= 6 and mobile_num < 8
for update
首先先匹配 mobile_num=6,此时会对索引 (3, 6] 加 Next Key Lock,虽然此时非唯一索引存在 , 但是不会退化为 Record Lock;其次再看后半部分的查询 mobile_num=8 , 需要对索引 (7, 9] 加 Next key Lock , 又因为 8 不存在,所以退化为 Gap Lock (7, 9) 。最终,需要对索引行加 Next key Lock (3, 6] 和 Gap Lock (7, 9) 。
2.1.3 意向锁(Intention Locks)
Innodb 为了支持多粒度锁定,引入了意向锁 。意向锁是一种表级锁,用于表明事务将要对某张表某行数据操作而进行的锁定 。同样,意向锁也分为类:共享意向锁(IS)和排他意向锁(IX) 。
名称符号描述共享意向锁IS表明事务将要对表的个别行设置共享锁排他意向锁IX表明事务将要对表的个别行设置排他锁
例如 select ... lock in shared mode 会设置共享意向锁 IS;select ... for update 会设置排他意向锁 IX
设置意向锁时需要按照以下两条原则进行设置:
1. 当事务需要申请行的共享锁 S 时,必须先对表申请共享意向 IS 锁或更强的锁
2. 当事务需要申请行的排他锁 X 时,必须先对表申请排他意向 IX 锁
?表级锁兼容性矩阵如下表:
将获取的锁(下)/ 已获取的锁(右)XIXSISX冲突冲突冲突冲突IX冲突兼容冲突兼容S冲突冲突兼容兼容IS冲突兼容兼容兼容
如果请求锁的事务与现有锁兼容,则会将锁授予该事务 , 但如果与现有锁冲突,则不会授予该事务 。事务等待,直到冲突的现有锁被释放 。
意向锁的目的就是为了说明事务正在对表的一行进行锁定,或将要对表的一行进行锁定 。在意向锁概念中,除了对全表加锁会导致意向锁阻塞外,其余情况意向锁均不会阻塞任何请求!
2.1.4 插入意向锁
插入意向锁是一种特殊的意向锁,同时也是一种特殊的 “Gap Lock”,是在 Insert 操作之前设置的 Gap Lock 。
如果此时有多个事务执行 insert 操作 , 恰好需要插入的位置都在同一个 Gap Lock 中,但是并不是在 Gap Lock 的同一个位置时,此时的插入意向锁彼此之间不会阻塞 。
2.2 过程分析
回到本文的问题上来 , 本文中有两个事务执行同样的动作,分别为先执行 select ... for update 获取排他锁,其次判断若为空 , 则执行 insert 动作,否则执行 update 动作 。伪代码描述如下:


推荐阅读