【MySQL】MySQL gap lock产生时机

1)提出问题:我们通常说在Repeate read下面,会有next-key lock(LOCK_ORDINARY)对应值0,而READ COMMITTED隔离级别下只会有记录锁LOCK_REC_NOT_GAP(对应值1024),那么什么时候会有gap lock(LOCK_GAP)对应值512?


2)官方的一个死锁例子(之所以会选这个例子,是因为这个例子非常典型,为了更好的效果,我将隔离级别设置为READ COMMITTED):

    创建表并插入数据
    CREATE TABLE `locktest6` (   `id` int(11) NOT NULL,   `a` int(11) NOT NULL,   `b` varchar(30) NOT NULL DEFAULT 'xddd',   PRIMARY KEY (`id`),   UNIQUE KEY `a` (`a`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

    INSERT INTO `locktest6` VALUES (3,2,'xddd'),(9,20,'ddd'),(12,13,'ddd'),(19,7,'cccc'),(20,5,'abcd'),(21,4,'fff');

    开启三个会话,会话的顺序是(1->2->3)
    session 1:
    



    session 2:
    

    session 3
    

    实验现象,这里session2、session3会阻塞(这里读者可以自己实验)

    现象分析:
    我们来看阻塞时的锁获取与等待情况
                            


这里唯一键a=17的二级索引加了LOCK_X|LOCK_REC_NOT_GAP|LOCK_REC锁,其次这条记录上面还有两把锁(LOCK_S|LOCK_ORDINARY|LOCK_WAITTING)在等待,分别是session2与session3的

这里的原因是:
session 1的insert 本来不加锁(是隐式锁,隐式锁是一种延迟加锁的策略);

当session插入a=17的记录时,发现与session1重复,这时session2会给session1加锁,锁为LOCK_X|LOCK_REC_NOT_GAP|LOCK_REC,同时把自己置为等待状态LOCK_S|LOCK_ORDINARY|LOCK_WAITTING,注意,这里为什么自己(session2)等待的时候是LOCK_S而不是LOCK_X呢,其实是为了提高并发;

session 3处于等待是因为session1 a=17的记录已经被session2加上了LOCK_X|LOCK_REC_NOT_GAP|LOCK_REC锁,而session3的a=17与session1的a=17重复,为了检测唯一性,也会将自己置为等待状态,锁为LOCK_S|LOCK_ORDINARY|LOCK_WAITTING


回滚session1,session3会成为死锁牺牲品,session2插入成功

session2

session3




这是的死锁情况以及加锁情况

这里我们发现了gap锁的踪迹,我们稍后分析
死锁牺牲session3之后,这时的session2的加锁情况

这里锁信息连到了一起,哈哈,如何解释呢,我们稍后分晓


先分析上面的死锁:
session 1 rollback后,二级索引记录为(33,17)的记录的被删除,上面的session2,session3上的LOCK_S|LOCK_ORDINARY|LOCK_WAITTING将他的gap锁分别迁移到下一条记录,即二级索引(20,9)的记录上由两把锁,分别为session2与session占有,锁为LOCK_S|LOCK_GAP|LOCK_REC,即死锁图中的lock mode s lock gap before rec;与此同时,session 2与session3在插入的时候,还要检测下一条记录上(即(20,9)的记录上)的锁是否与LOCK_X|LOCK_GAP|LOCK_INTENSION冲突,这样就形成了session2的LOCK_X|LOCK_GAP|LOCK_INTENSION在等待session3 的LOCK_S|LOCK_GAP|LOCK_REC释放,而session3的LOCK_X|LOCK_GAP|LOCK_INTENSION则在等待session2的LOCK_S|LOCK_GAP|LOCK_REC释放,这样就形成了死锁,这里事务选择回滚了session3

再分析session2的加锁情况:
session3回滚后 ,session2获得了LOCK_X|LOCK_GAP|LOCK_INTENSION锁与LOCK_S|LOCK_GAP|LOCK_REC,这个锁加在记录(20,9)上;heap no 8的记录即session2插入的记录也获得了LOCK_S|LOCK_GAP|LOCK_REC锁,这是因为插入时,会进行锁分裂,将heap no 8下一条记录的(20,9)的锁LOCK_S|LOCK_GAP|LOCK_REC迁移到了heap no 8的记录上;为啥上面的锁信息会连在一起呢,即既有LOCK_S|LOCK_ORDINARY|LOCK_REC又有LOCK_S|LOCK_GAP|LOCK_REC呢,答案是:这里的LOCK_S|LOCK_ORDINARY|LOCK_REC只是表示下面行LOCK_S|LOCK_GAP|LOCK_REC有LOCK_S|LOCK_ORDINARY|LOCK_REC继承而来,并不表示记录上还加了LOCK_S|LOCK_ORDINARY|LOCK_REC锁


3)总结
    gap锁一般很少见到,当唯一索引检测重复的值的时候,往往会产生gap锁;另外当可重复读隔离级别下,做等值查询时,也会碰到gap锁(这个读者可以自己去实验),时间仓促,没有整理。


请使用浏览器的分享功能分享到微信等