不会锁定gap。
S锁读取(SELECT ... LOCK IN SHARE MODE
),X锁读取(SELECT ... FOR UPDATE
)、更新UPDATE
和删除DELETE
这四类语句,采用的锁取决于搜索条件中使用的索引类型。
UPDATE ... WHERE ...
在搜索遇到的每条记录上设置一个独占的next-key锁,如果是唯一索引只锁定记录。
当UPDATE
修改聚簇索引时,将对受影响的二级索引采用隐式锁,隐式锁是在索引中对二级索引的记录逻辑加锁,实际上不产生锁对象,不占用内存空间。
UPDATE
可能会导致新的普通索引的插入。当新的索引插入之前,会首先执行一次重复索引检查。在重复检查和插入时,更新操作会对受影响的二级索引记录采用共享锁定(S锁)。
DELETE FROM ... WHERE ...
在搜索遇到的每条记录上设置一个独占的next-key锁,如果是唯一索引只锁定记录。
INSERT
区别于UPDATE系列单独列出,是因为它的处理方式较为特别。
插入行之前,会设置一种插入意向锁,插入意向锁表示插入的意图。如果其它事务在要插入的位置上设置了X锁,则无法获取插入意向锁,插入操作也因此阻塞。
INSERT
在插入的行上设置X锁。该锁是一个Record锁,并不是next-key锁,即只锁定记录本身,不锁定间隙,因此不会阻止其他会话在这行记录前的间隙中插入新的记录。
具体的加锁过程,见6.2。
并发条件下,唯一键索引冲突可能会导致死锁,这种死锁一般分为两种,一种是rollback
引发,另一种是commit
引发。
我命名为insert-insert-insert-rollback死锁
当事务一执行回滚时,事务二和事务三发生了死锁。InnoDB的死锁检测一旦检测到死锁发生,会自动失败其中一个事务,因此看到的结果是一个失败另一个成功。
为什么会死锁?
事务二和事务三为什么会加S锁,而不是直接等待X锁
delete-insert-insert-commit死锁
这种情况下产生的死锁和insert-insert-insert-rollback
死锁产生的原理一致。
经过以上分析,一条数据在插入时经过以下几个过程:
假设数据表test.test
中存在(1,1)、(5,5)和(10,10)三条记录。
仍然是表test,当前表中的记录如下:
使用show engine innodb status
查看死锁状态。先后出现lock_mode X locks gap before rec insert intention waiting
和lock_mode X locks gap before rec
字眼,是gap锁和插入意向锁的冲突导致的死锁。
首先回顾一下两个事务中的select ... for update
做了哪些加锁操作。
由gap锁的特性,兼容矩阵中冲突的锁也可以被不同的事务同时加在一个间隙上。上述两个select ... for update
语句出现了间隙锁的交集,code=5
的next-key锁和code=10
的gap锁有重叠的区域——(5,10)。
当事务一执行插入语句时,会先加X模式的插入意向锁
,即兼容矩阵中的IX锁。
但是由于插入意向锁要锁定的位置存在X模式的gap锁
。兼容矩阵中IX和X锁是不兼容的,因此事务一的IX锁会等待事务二的gap锁释放。
事务二也执行插入语句,与事务一同样,事务二的插入意向锁IX锁会等待事务一的gap锁释放。
两个事务互相等待对方先释放锁,因此出现死锁。
除了以上给出的几种死锁模式,还有很多其他死锁的场景。
无论是哪种场景,万变不离其宗,都是由于某个区间上或者某一个记录上可以同时持有锁,例如不同事务在同一个间隙gap上的锁不冲突;不同事务中,S锁可以阻塞X锁的获取,但是不会阻塞另一个事务获取该S锁。这样才会出现两个事务同时持有锁,并互相等待,最终导致死锁。
其中需要注意的点是,增、删、改的操作都会进行一次当前读操作,以此获取最新版本的数据,并检测是否有重复的索引。
这个过程除了会导致RR隔离级别下出现死锁之外还会导致其他两个问题: