两个 INSERT 发生死锁原因剖析

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

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

  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 是最流行的关系型数据库管理系统之一。

    675 引用 • 535 回帖 • 1 关注
  • insert
    2 引用 • 1 回帖
  • 死锁
    3 引用 • 1 回帖

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • gRpc
    10 引用 • 8 回帖 • 54 关注
  • 爬虫

    网络爬虫(Spider、Crawler),是一种按照一定的规则,自动地抓取万维网信息的程序。

    106 引用 • 275 回帖 • 2 关注
  • 互联网

    互联网(Internet),又称网际网络,或音译因特网、英特网。互联网始于 1969 年美国的阿帕网,是网络与网络之间所串连成的庞大网络,这些网络以一组通用的协议相连,形成逻辑上的单一巨大国际网络。

    96 引用 • 330 回帖
  • webpack

    webpack 是一个用于前端开发的模块加载器和打包工具,它能把各种资源,例如 JS、CSS(less/sass)、图片等都作为模块来使用和处理。

    41 引用 • 130 回帖 • 295 关注
  • PHP

    PHP(Hypertext Preprocessor)是一种开源脚本语言。语法吸收了 C 语言、 Java 和 Perl 的特点,主要适用于 Web 开发领域,据说是世界上最好的编程语言。

    164 引用 • 407 回帖 • 527 关注
  • 学习

    “梦想从学习开始,事业从实践起步” —— 习近平

    161 引用 • 473 回帖 • 1 关注
  • DNSPod

    DNSPod 建立于 2006 年 3 月份,是一款免费智能 DNS 产品。 DNSPod 可以为同时有电信、网通、教育网服务器的网站提供智能的解析,让电信用户访问电信的服务器,网通的用户访问网通的服务器,教育网的用户访问教育网的服务器,达到互联互通的效果。

    6 引用 • 26 回帖 • 521 关注
  • Netty

    Netty 是一个基于 NIO 的客户端-服务器编程框架,使用 Netty 可以让你快速、简单地开发出一个可维护、高性能的网络应用,例如实现了某种协议的客户、服务端应用。

    49 引用 • 33 回帖 • 23 关注
  • Postman

    Postman 是一款简单好用的 HTTP API 调试工具。

    4 引用 • 3 回帖
  • Quicker

    Quicker 您的指尖工具箱!操作更少,收获更多!

    20 引用 • 73 回帖 • 2 关注
  • 运维

    互联网运维工作,以服务为中心,以稳定、安全、高效为三个基本点,确保公司的互联网业务能够 7×24 小时为用户提供高质量的服务。

    148 引用 • 257 回帖
  • 链滴

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

    记录生活,连接点滴

    131 引用 • 3639 回帖
  • DevOps

    DevOps(Development 和 Operations 的组合词)是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。

    40 引用 • 24 回帖
  • TextBundle

    TextBundle 文件格式旨在应用程序之间交换 Markdown 或 Fountain 之类的纯文本文件时,提供更无缝的用户体验。

    1 引用 • 2 回帖 • 47 关注
  • SSL

    SSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS 与 SSL 在传输层对网络连接进行加密。

    69 引用 • 190 回帖 • 495 关注
  • RESTful

    一种软件架构设计风格而不是标准,提供了一组设计原则和约束条件,主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

    30 引用 • 114 回帖
  • 域名

    域名(Domain Name),简称域名、网域,是由一串用点分隔的名字组成的 Internet 上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位(有时也指地理位置)。

    43 引用 • 208 回帖 • 2 关注
  • Bootstrap

    Bootstrap 是 Twitter 推出的一个用于前端开发的开源工具包。它由 Twitter 的设计师 Mark Otto 和 Jacob Thornton 合作开发,是一个 CSS / HTML 框架。

    18 引用 • 33 回帖 • 684 关注
  • JSON

    JSON (JavaScript Object Notation)是一种轻量级的数据交换格式。易于人类阅读和编写。同时也易于机器解析和生成。

    51 引用 • 190 回帖 • 1 关注
  • Love2D

    Love2D 是一个开源的, 跨平台的 2D 游戏引擎。使用纯 Lua 脚本来进行游戏开发。目前支持的平台有 Windows, Mac OS X, Linux, Android 和 iOS。

    14 引用 • 53 回帖 • 513 关注
  • 创造

    你创造的作品可能会帮助到很多人,如果是开源项目的话就更赞了!

    173 引用 • 990 回帖
  • Flutter

    Flutter 是谷歌的移动 UI 框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。 Flutter 可以与现有的代码一起工作,它正在被越来越多的开发者和组织使用,并且 Flutter 是完全免费、开源的。

    39 引用 • 92 回帖 • 8 关注
  • GitLab

    GitLab 是利用 Ruby 一个开源的版本管理系统,实现一个自托管的 Git 项目仓库,可通过 Web 界面操作公开或私有项目。

    46 引用 • 72 回帖
  • jsoup

    jsoup 是一款 Java 的 HTML 解析器,可直接解析某个 URL 地址、HTML 文本内容。它提供了一套非常省力的 API,可通过 DOM,CSS 以及类似于 jQuery 的操作方法来取出和操作数据。

    6 引用 • 1 回帖 • 462 关注
  • 电影

    这是一个不能说的秘密。

    120 引用 • 597 回帖
  • 前端

    前端技术一般分为前端设计和前端开发,前端设计可以理解为网站的视觉设计,前端开发则是网站的前台代码实现,包括 HTML、CSS 以及 JavaScript 等。

    247 引用 • 1347 回帖
  • HBase

    HBase 是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的 Google 论文 “Bigtable:一个结构化数据的分布式存储系统”。就像 Bigtable 利用了 Google 文件系统所提供的分布式数据存储一样,HBase 在 Hadoop 之上提供了类似于 Bigtable 的能力。

    17 引用 • 6 回帖 • 45 关注