两个 INSERT 发生死锁原因剖析

本贴最后更新于 2352 天前,其中的信息可能已经时移世异

开始之前,关于锁、死锁,我们要先统一下几点认知:

  1. 死锁是由于多个事务相互持有其他事务所需要的锁,结果导致事务都无法继续,进而触发死锁检测,其中某个事务会被回滚,释放相应的锁,其他事务得以正常继续;简言之,就是多个事务之间的锁等待产生了回路,死循环了;
  2. 死锁发生时,会立刻被检测到,并且回滚其中某个事务,而不会长时间阻塞、等待;
  3. 从 MySQL 5.7.15 开始,新增选项 innodb_deadlock_detect,没记错的话应该是阿里团队率先实现的。当它设置为 OFF 时(默认值是 ON),InnoDB 会不检测死锁,在高并发场景(例如“秒杀”)业务中特别有用,可以有效提高事务并发性能;
  4. 在启用死锁检测时,InnoDB 默认的最大检测深度为 200,在上面提到的高并发高竞争场景下,在热点数据上的锁等待队列可能很长,死锁检测代价很大。或者当等待队列中所有的行锁总数超过 100 万 时,也会被认为认为发生死锁了,直接触发死锁检测处理机制;
  5. InnoDB 行锁等待超时默认为 50 秒,一般建议设置 5-10 秒就够了;
  6. 有时候,可能会口误把 长时间的行锁等待 说成是 死锁,其实二者完全不一样,不要犯这种常识性口误。

好了,正式开始今天的案例。

先看测试表:

mysql> show create table ld\G *************************** 1. row *************************** Table: ld Create Table: CREATE TABLE `ld` ( `id` int(11) NOT NULL DEFAULT '0', `name` varchar(10) DEFAULT NULL, UNIQUE KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 1 row in set (0.00 sec) mysql> select * from ld; +----+-------+ | id | name | +----+-------+ | 1 | jerry | +----+-------+ 1 row in set (0.00 sec)

然后我们执行下面的测试:

session1 session2 session3
begin;delete from ld where id=1;
begin;insert into ld select 1,'dkey';
begin;insert into ld select 1,’mark';
commit;
Query OK, 1 row affected (11.82 sec)Records: 1 Duplicates: 0 Warnings: 0 ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

这时候我们看下 InnoDB STATUS 的输出:

------------------------ LATEST DETECTED DEADLOCK ------------------------ 2017-09-05 04:50:42 0x7f233f088700 *** (1) TRANSACTION: TRANSACTION 5415714, ACTIVE 19 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 1430, OS thread handle 139789350385408, query id 9663235 localhost root executing insert into ld select 1,'dkey' *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 130 page no 3 n bits 72 index id of table `sbtest`.`ld` trx id 5415714 lock_mode X locks rec but not gap waiting Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 32 0: len 4; hex 80000001; asc ;; 1: len 6; hex 00000052a31d; asc R ;; 2: len 7; hex 670000038517ea; asc g ;; 3: len 5; hex 6a65727279; asc jerry;; *** (2) TRANSACTION: TRANSACTION 5415715, ACTIVE 10 sec inserting mysql tables in use 1, locked 1 3 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 1431, OS thread handle 139789358106368, query id 9663237 localhost root executing insert into ld select 1,'mark' *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 130 page no 3 n bits 72 index id of table `sbtest`.`ld` trx id 5415715 lock mode S (注意S锁) Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 32 0: len 4; hex 80000001; asc ;; 1: len 6; hex 00000052a31d; asc R ;; 2: len 7; hex 670000038517ea; asc g ;; 3: len 5; hex 6a65727279; asc jerry;; *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 130 page no 3 n bits 72 index id of table `sbtest`.`ld` trx id 5415715 lock_mode X locks rec but not gap waiting Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 32 0: len 4; hex 80000001; asc ;; 1: len 6; hex 00000052a31d; asc R ;; 2: len 7; hex 670000038517ea; asc g ;; 3: len 5; hex 6a65727279; asc jerry;;

从上面这个输出来看,我们看到的现场是两个 insert 请求发生了死锁。单纯看这 2 个 SQL 的话,应该是产生锁等待才对,而不是死锁。

按照我们常规理解,session1 未 commit 前,应该是持有 id=1 上的 record lock(X),而 session2 和 session3 则都在等待这个锁的释放。而实际上呢,肯定不是这样的,否则也不至于发生死锁了。

这次的案例其实在 MySQL 官方文档上已经解释过了,而且也给了演示案例(如本例)。文档中是这么说的:

INSERT sets an exclusive lock on the inserted row. This lock is an index-record lock, not a next-key lock (that is, there is no gap lock) and does not prevent other sessions from inserting into the gap before the inserted row.

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. Suppose that there are index records with values of 4 and 7. Separate transactions that attempt to insert values of 5 and 6 each lock the gap between 4 and 7 with insert intention locks prior to obtaining the exclusive lock on the inserted row, but do not block each other because the rows are nonconflicting.

If a duplicate-key error occurs, a shared lock on the duplicate index record is set. This use of a shared lock can result in deadlock should there be multiple sessions trying to insert the same row if another session already has an exclusive lock. This can occur if another session deletes the row.

核心内容是:当需要进行唯一性冲突检测时,需要先加一个 S 锁。

这样的话,上面案例的加锁过程就不是之前推测的那样,而是像下面这样了:

session1 session2 session3
begin;delete from ld where id=1;持有 id=1 的 record lock(X)
begin;insert into ld select 1,’dkey';需要判断唯一性,检测到冲突,请求 id=1 的 next-key lock(S)被阻塞,等待ing
begin;insert into ld select 1,’mark';需要判断唯一性,检测到冲突,请求 id=1 的 next-key lock(S)被阻塞,等待ing
commit;提交,释放 id=1 上的锁
后面 session3 检测到死锁冲突后,session2 才 insert 成功;Query OK, 1 row affected (11.82 sec)Records: 1 Duplicates: 0 Warnings: 0 成功获取 id=1 的 next-key lock(S);请求 id=1 的 record lock(X)锁;触发死锁检测,失败、回滚;ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

下面是另一个类似的案例:

session1 session2
begin;select * from d where id = 1 lock in share mode;持有 id=1 上的 record lock(S) -
- begin;select * from d where id = 1 lock in share mode;持有 id=1 上的 record lock(S)
delete from d where id = 1;请求 id=1 上的 record lock(X),被 session2 阻塞了,等待中 -
- delete from d where id = 1;请求 id=1 上的 record lock(X),检测到死锁,失败,回滚

通过上面这两个案例,其实想要告诉大家的是:发生死锁时,不能只看现场,还得分析过程,才能知道真正的原因,死锁发生的原因也并不复杂,但是得能想办法还原过程。

下面提供一个更加诡异的死锁案例,这个死锁案例出现了 S GAP 锁,可能从来没有见过。

mysql> create table testunj1 (id1 int primary key,id2 int unique key,name varchar(20)); mysql> insert into testunj1 values(1,1,'gaopeng'),(10,10,'gaopeng'),(20,20,'gaopeng'); mysql> select * from testunj1; +-----+------+---------+ | id1 | id2 | name | +-----+------+---------+ | 1 | 1 | gaopeng | | 10 | 10 | gaopeng | | 20 | 20 | gaopeng | +-----+------+---------+ 3 rows in set (0.01 sec)

死锁模拟:

session1 session2 session3
begin;insert into testunj1 values(17,17,’gaopeng’);insert into testunj1 values(15,15,’gaopeng’);
begin;insert into testunj1 values(14,15,’gaopeng’);堵塞
begin;insert into ld select 1,’mark';需要判断唯一性,检测到冲突,请求 id=1 的 next-key lock(S)被阻塞,等待ing
commit;提交,释放 id=1 上的锁
后面 session3 检测到死锁冲突后,session2 才 insert 成功;Query OK, 1 row affected (11.82 sec)Records: 1 Duplicates: 0 Warnings: 0 成功获取 id=1 的 next-key lock(S);请求 id=1 的 record lock(X)锁;触发死锁检测,失败、回滚;ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

这时候我们看下 InnoDB STATUS 的输出:

------------------------ LATEST DETECTED DEADLOCK ------------------------ 2017-09-05 05:53:59 0x7f233f088700 *** (1) TRANSACTION: TRANSACTION 5415743, ACTIVE 20 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 4 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1 MySQL thread id 1430, OS thread handle 139789350385408, query id 9663296 localhost root update insert into testunj1 values(14,15,'gaopeng') *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 131 page no 4 n bits 72 index id2 of table `sbtest`.`testunj1` trx id 5415743 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000014; asc ;; 1: len 4; hex 80000014; asc ;; *** (2) TRANSACTION: TRANSACTION 5415744, ACTIVE 6 sec inserting mysql tables in use 1, locked 1 4 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1 MySQL thread id 1431, OS thread handle 139789358106368, query id 9663298 localhost root update insert into testunj1 values(16,17,'gaopeng') *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 131 page no 4 n bits 72 index id2 of table `sbtest`.`testunj1` trx id 5415744 lock mode S locks gap before rec Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000014; asc ;; 1: len 4; hex 80000014; asc ;; *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 131 page no 4 n bits 72 index id2 of table `sbtest`.`testunj1` trx id 5415744 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000014; asc ;; 1: len 4; hex 80000014; asc ;;

可以看到 lock mode S locks gap,完结.

转载至:《两个 INSERT 发生死锁原因剖析》

  • MySQL

    MySQL 是一个关系型数据库管理系统,由瑞典 MySQL AB 公司开发,目前属于 Oracle 公司。MySQL 是最流行的关系型数据库管理系统之一。

    693 引用 • 537 回帖
  • insert
    2 引用 • 1 回帖
  • 死锁
    3 引用 • 1 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • TensorFlow

    TensorFlow 是一个采用数据流图(data flow graphs),用于数值计算的开源软件库。节点(Nodes)在图中表示数学操作,图中的线(edges)则表示在节点间相互联系的多维数据数组,即张量(tensor)。

    20 引用 • 19 回帖 • 4 关注
  • 开源中国

    开源中国是目前中国最大的开源技术社区。传播开源的理念,推广开源项目,为 IT 开发者提供了一个发现、使用、并交流开源技术的平台。目前开源中国社区已收录超过两万款开源软件。

    7 引用 • 86 回帖
  • Wide

    Wide 是一款基于 Web 的 Go 语言 IDE。通过浏览器就可以进行 Go 开发,并有代码自动完成、查看表达式、编译反馈、Lint、实时结果输出等功能。

    欢迎访问我们运维的实例: https://wide.b3log.org

    30 引用 • 218 回帖 • 636 关注
  • CSS

    CSS(Cascading Style Sheet)“层叠样式表”是用于控制网页样式并允许将样式信息与网页内容分离的一种标记性语言。

    199 引用 • 543 回帖 • 2 关注
  • LaTeX

    LaTeX(音译“拉泰赫”)是一种基于 ΤΕΧ 的排版系统,由美国计算机学家莱斯利·兰伯特(Leslie Lamport)在 20 世纪 80 年代初期开发,利用这种格式,即使使用者没有排版和程序设计的知识也可以充分发挥由 TeX 所提供的强大功能,能在几天,甚至几小时内生成很多具有书籍质量的印刷品。对于生成复杂表格和数学公式,这一点表现得尤为突出。因此它非常适用于生成高印刷质量的科技和数学类文档。

    13 引用 • 57 回帖 • 6 关注
  • Elasticsearch

    Elasticsearch 是一个基于 Lucene 的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于 RESTful 接口。Elasticsearch 是用 Java 开发的,并作为 Apache 许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

    117 引用 • 99 回帖 • 199 关注
  • NetBeans

    NetBeans 是一个始于 1997 年的 Xelfi 计划,本身是捷克布拉格查理大学的数学及物理学院的学生计划。此计划延伸而成立了一家公司进而发展这个商用版本的 NetBeans IDE,直到 1999 年 Sun 买下此公司。Sun 于次年(2000 年)六月将 NetBeans IDE 开源,直到现在 NetBeans 的社群依然持续增长。

    78 引用 • 102 回帖 • 701 关注
  • OneNote
    1 引用 • 3 回帖
  • Sublime

    Sublime Text 是一款可以用来写代码、写文章的文本编辑器。支持代码高亮、自动完成,还支持通过插件进行扩展。

    10 引用 • 5 回帖 • 2 关注
  • RIP

    愿逝者安息!

    8 引用 • 92 回帖 • 399 关注
  • Access
    1 引用 • 3 回帖 • 1 关注
  • 酷鸟浏览器

    安全 · 稳定 · 快速
    为跨境从业人员提供专业的跨境浏览器

    3 引用 • 59 回帖 • 49 关注
  • InfluxDB

    InfluxDB 是一个开源的没有外部依赖的时间序列数据库。适用于记录度量,事件及实时分析。

    2 引用 • 94 关注
  • 人工智能

    人工智能(Artificial Intelligence)是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门技术科学。

    170 引用 • 315 回帖
  • 京东

    京东是中国最大的自营式电商企业,2015 年第一季度在中国自营式 B2C 电商市场的占有率为 56.3%。2014 年 5 月,京东在美国纳斯达克证券交易所正式挂牌上市(股票代码:JD),是中国第一个成功赴美上市的大型综合型电商平台,与腾讯、百度等中国互联网巨头共同跻身全球前十大互联网公司排行榜。

    14 引用 • 102 回帖 • 316 关注
  • etcd

    etcd 是一个分布式、高可用的 key-value 数据存储,专门用于在分布式系统中保存关键数据。

    6 引用 • 26 回帖 • 543 关注
  • CentOS

    CentOS(Community Enterprise Operating System)是 Linux 发行版之一,它是来自于 Red Hat Enterprise Linux 依照开放源代码规定释出的源代码所编译而成。由于出自同样的源代码,因此有些要求高度稳定的服务器以 CentOS 替代商业版的 Red Hat Enterprise Linux 使用。两者的不同在于 CentOS 并不包含封闭源代码软件。

    239 引用 • 224 回帖 • 1 关注
  • RYMCU

    RYMCU 致力于打造一个即严谨又活泼、专业又不失有趣,为数百万人服务的开源嵌入式知识学习交流平台。

    4 引用 • 6 回帖 • 57 关注
  • WebClipper

    Web Clipper 是一款浏览器剪藏扩展,它可以帮助你把网页内容剪藏到本地。

    3 引用 • 9 回帖 • 1 关注
  • React

    React 是 Facebook 开源的一个用于构建 UI 的 JavaScript 库。

    196 引用 • 291 回帖 • 373 关注
  • Visio
    1 引用 • 2 回帖 • 1 关注
  • 链滴

    链滴是一个记录生活的地方。

    记录生活,连接点滴

    176 引用 • 3859 回帖
  • frp

    frp 是一个可用于内网穿透的高性能的反向代理应用,支持 TCP、UDP、 HTTP 和 HTTPS 协议。

    20 引用 • 7 回帖
  • SendCloud

    SendCloud 由搜狐武汉研发中心孵化的项目,是致力于为开发者提供高质量的触发邮件服务的云端邮件发送平台,为开发者提供便利的 API 接口来调用服务,让邮件准确迅速到达用户收件箱并获得强大的追踪数据。

    2 引用 • 8 回帖 • 501 关注
  • GitHub

    GitHub 于 2008 年上线,目前,除了 Git 代码仓库托管及基本的 Web 管理界面以外,还提供了订阅、讨论组、文本渲染、在线文件编辑器、协作图谱(报表)、代码片段分享(Gist)等功能。正因为这些功能所提供的便利,又经过长期的积累,GitHub 的用户活跃度很高,在开源世界里享有深远的声望,并形成了社交化编程文化(Social Coding)。

    210 引用 • 2040 回帖 • 1 关注
  • abitmean

    有点意思就行了

    33 关注
  • Webswing

    Webswing 是一个能将任何 Swing 应用通过纯 HTML5 运行在浏览器中的 Web 服务器,详细介绍请看 将 Java Swing 应用变成 Web 应用

    1 引用 • 15 回帖 • 638 关注