故障分析 | MySQL Insert 加锁与死锁分析

前  言


本文是由爱可生运维团队出品的「MySQL专栏」系列文章,内容来自于运维团队一线实战经验,涵盖MySQL各类特性的实践,优化案例,数据库架构,HA,监控等,有扫雷功效。数据库


爱可生开源社区持续运营维护的小目标:网络


  • 每周至少推送一篇高质量技术文章session

  • 每个月研发团队发布开源组件新版架构

  • 每一年1024开源一款企业级组件app

  • 2019年至少25场社区活动运维


欢迎你们持续关注~ide


图片

 

在咱们尝试回答这个问题前,必定要注意前提条件,若是你看过登博的《MySQL 加锁处理》,必定知道前提不一样答案也就不一样,若是还没看过建议你去看一下,连接:学习

http://hedengcheng.com/?p=771


那么这个问题缺乏哪些前提条件?优化

 

  • 1. c2 字段建有惟一索引spa

  • 2. 隔离级别为:READ-COMMITTED

 

其实网络上有相似的案例分析,其中丁奇老师在《MySQL实战45讲》中的第40篇《insert语句的锁为何这么多?》中有同样的例子和分析,可是个人理解有些微差别,因此来讲说我我的的见解,若是有不对的地方请你们指正。首先我会分析一下这个场景的加锁状况和死锁缘由,而后对于差别的点进行展开,最后总结 insert 的加锁状况(关于 insert 的加锁行为,其实不像 delete 那样简单清晰,里面有一些须要注意的点)。

 

加锁状况与死锁缘由分析


为方便你们复现,完整表结构和数据以下:

 

CREATE TABLE `t3` (
`c1` int(11) NOT NULL AUTO_INCREMENT,
`c2` int(11) DEFAULT NULL,
PRIMARY KEY (`c1`),
UNIQUE KEY `c2` (`c2`)
) ENGINE=InnoDB

insert into t3 values(1,1),(15,15),(20,20);


在 session1 执行 commit 的瞬间,咱们会看到 session二、session3 的其中一个报死锁。这个死锁是这样产生的:

 

  • 1. session1 执行 delete  会在惟一索引 c2 的 c2 = 15 这一记录上加 X lock(也就是在MySQL 内部观测到的:X Lock but not gap);

  • 2. session2 和 session3 在执行 insert 的时候,因为惟一约束检测发生惟一冲突,会加 S Next-Key Lock,即对 (1,15] 这个区间加锁包括间隙,而且被 seesion1 的 X Lock 阻塞,进入等待;

  • 3. session1 在执行 commit 后,会释放 X Lock,session2 和 session3 都得到 S Next-Key Lock;

  • 4. session2 和 session3 继续执行插入操做,这个时候 INSERT INTENTION LOCK(插入意向锁)出现了,而且因为插入意向锁会被 gap 锁阻塞,因此 session2 和 session3 互相等待,形成死锁。

 

死锁日志以下:

 

 

INSERT INTENTION LOCK


在以前的死锁分析第四点,若是不分析插入意向锁,也是会形成死锁的,由于插入最终仍是要对记录加 X Lock 的,session2 和 session3 仍是会互相阻塞互相等待。

 

可是插入意向锁是客观存在的,咱们能够在官方手册中查到,不可忽略:

 

Prior to inserting the row, a type of gap lock called an insert intention gap lock is set. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap.

 

插入意向锁实际上是一种特殊的 gap lock,可是它不会阻塞其余锁。假设存在值为 4 和 7 的索引记录,尝试插入值 5 和 6 的两个事务在获取插入行上的排它锁以前使用插入意向锁锁定间隙,即在(4,7)上加 gap lock,可是这两个事务不会互相冲突等待。

 

当插入一条记录时,会去检查当前插入位置的下一条记录上是否存在锁对象,若是下一条记录上存在锁对象,就须要判断该锁对象是否锁住了 gap。若是 gap 被锁住了,则插入意向锁与之冲突,进入等待状态(插入意向锁之间并不互斥)。总结一下这把锁的属性:

 

  • 1. 它不会阻塞其余任何锁;

  • 2. 它自己仅会被 gap lock 阻塞。

 

在学习 MySQL 过程当中,通常只有在它被阻塞的时候才能观察到,因此这也是它经常被忽略的缘由吧...

 

GAP LOCK

 

在此例中,另一个重要的点就是 gap lock,一般状况下咱们说到 gap lock 都只会联想到 REPEATABLE-READ 隔离级别利用其解决幻读。但实际上在 READ-COMMITTED 隔离级别,也会存在 gap lock ,只发生在:惟一约束检查到有惟一冲突的时候,会加 S Next-key Lock,即对记录以及与和上一条记录之间的间隙加共享锁。

 

经过下面这个例子就能验证:

 

 

这里 session1 插入数据遇到惟一冲突,虽然报错,可是对 (15,20] 加的 S Next-Key Lock 并不会立刻释放,因此 session2 被阻塞。另一种状况就是本文开始的例子,当 session2 插入遇到惟一冲突可是由于被 X Lock 阻塞,并不会马上报错 “Duplicate key”,可是依然要等待获取 S Next-Key Lock 。

 

有个困惑好久的疑问:出现惟一冲突须要加 S Next-Key Lock 是事实,可是加锁的意义是什么?仍是说是经过 S Next-Key Lock 来实现的惟一约束检查,可是这样意味着在插入没有遇到惟一冲突的时候,这个锁会马上释放,这不符合二阶段锁原则。这点但愿能与你们一块儿讨论获得好的解释。

 

若是是在 REPEATABLE-READ,除以上所说的惟一约束冲突外,gap lock 的存在是这样的:

 

普通索引(非惟一索引)的S/X Lock,都带 gap 属性,会锁住记录以及前1条记录到后1条记录的左闭右开区间,好比有[4,6,8]记录,delete 6,则会锁住[4,8)整个区间

 

对于 gap lock,相信 DBA 们的心情是同样同样的,因此个人建议是

 

  • 1. 在绝大部分的业务场景下,均可以把 MySQL 的隔离界别设置为 READ-COMMITTED;

  • 2. 在业务方便控制字段值惟一的状况下,尽可能减小表中惟一索引的数量。

 

锁冲突矩阵


前面咱们说的 GAP LOCK 实际上是锁的属性,另外咱们知道 InnoDB 常规锁模式有:S 和 X,即共享锁和排他锁。锁模式和锁属性是能够随意组合的,组合以后的冲突矩阵以下,这对咱们分析死锁颇有帮助:

 

图片

 

INSERT 加锁总结


无 Unique Key:X Lock but not gap

有 Unique Key:


  • 惟一性约束检查发生冲突时,会加 S Lock,带 gap 属性,会锁住记录以及与前1条记录以前的间隙;

  • 若是插入的位置有带 gap 属性的 S/X Lock,则插入意向锁(LOCK_INSERT_INTENTION)被阻塞,进入等待状态;

  • 若是新数据顺利插入,最后对记录加 X Lock but not gap。


select、delete 加锁行为是很简单的,刚咱们看了 insert 的加锁稍有点复杂,那么 update 是怎么加锁的呢?或者更复杂一点的 replace into 呢?欢迎你们一块儿讨论。

相关文章
相关标签/搜索