两个INSERT发生死锁原因剖析

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介:

两个INSERT也能发生死锁?貌似不可思议,实际上是正常的。

本文整理过程中,先后向高鹏、王少华、苏斌等几位朋友请教确认,感谢。

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

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

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

先看测试表DDL:

yejr@imysql.com [yejr]>show create table d\G
********************** 1. row **********************
       Table: d
Create Table: CREATE TABLE `d` (
  `i` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`i`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

yejr@imysql.com [yejr]>select * from d;
+---+
| i |
+---+
| 1 |
+---+

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


image


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

------------------------
LATEST DETECTED DEADLOCK
------------------------
2017-09-02 14:59:08 0x700004208000
*** (1) TRANSACTION:
TRANSACTION 274616, ACTIVE 12 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 16, OS thread handle 123145373167616, query id 398 localhost root executing
insert into d select 1
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:RECORD LOCKS space id 510 page no 3 n bits 72 index PRIMARY of table `yejr`.`d` trx id 274616 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 32
 0: len 4; hex 80000001; asc     ;;
 1: len 6; hex 0000000430b3; asc     0 ;;
 2: len 7; hex 3b0000018027a4; asc ;    ' ;;

*** (2) TRANSACTION:
TRANSACTION 274617, ACTIVE 4 sec inserting
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 18, OS thread handle 123145371549696, query id 400 localhost root executing
insert into d select 1
*** (2) HOLDS THE LOCK(S):RECORD LOCKS space id 510 page no 3 n bits 72 index PRIMARY of table `yejr`.`d` trx id 274617 lock mode S(想想,哪里冒出来的S锁?)Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 32
 0: len 4; hex 80000001; asc     ;;
 1: len 6; hex 0000000430b3; asc     0 ;;
 2: len 7; hex 3b0000018027a4; asc ;    ' ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:RECORD LOCKS space id 510 page no 3 n bits 72 index PRIMARY of table `yejr`.`d` trx id 274617 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 32
 0: len 4; hex 80000001; asc     ;;
 1: len 6; hex 0000000430b3; asc     0 ;;
 2: len 7; hex 3b0000018027a4; asc ;    ' ;;

*** WE ROLL BACK TRANSACTION (2)

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

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

关于InnoDB行锁更详细的知识点我们以后找时间再说。这次的案例其实在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 锁。

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

image


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


image


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

原文发布时间为:2017-09-05
本文来自云栖社区合作伙伴“老叶茶馆”,了解相关信息可以关注“老叶茶馆”微信公众号

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
执行ALTER TABLE语句时如何避免长时间阻塞并发查询
执行ALTER TABLE语句时如何避免长时间阻塞并发查询
213 0
|
关系型数据库 MySQL 索引
MySQL InnoDB中的锁-插入意向锁(Insert Intention Lock)
MySQL InnoDB 插入意向锁 Insert Intention Lock
3646 0
MySQL InnoDB中的锁-插入意向锁(Insert Intention Lock)
|
NoSQL 关系型数据库 MySQL
MySQL 常见死锁场景 -- 并发Replace into导致死锁
### MySQL Replace into issue MySQL 并发 Replace into 引起死锁问题 在之前的文章 [#issue 68021 MySQL unique check 问题](https://zhuanlan.zhihu.com/p/503880736)中, 我们已经介绍了在 MySQL 里面, 由于唯一键的检查(unique check), 导致 MySQ
479 0
|
SQL 关系型数据库 MySQL
INSERT ... FOR UPDATE这把锁你用过没?
INSERT ... FOR UPDATE这把锁你用过没?
322 0
|
SQL 关系型数据库 MySQL
并发replace操作导致的死锁问题
并发replace操作导致的死锁问题
216 0
|
存储 SQL 关系型数据库
SQL 事务与锁 详解
SQL 事务与锁 详解
SQL 事务与锁 详解
|
8月前
|
关系型数据库 MySQL 数据库
MySQL 并发 insert on duplicate key 导致的死锁
一 前言死锁,其实是一个很有意思也很有挑战的技术问题,大概每个DBA和部分开发同学都会在工作过程中遇见 。关于死锁我会持续写一个系列的案例分析,希望能够对想了解死锁的朋友有所帮助。二 案例分析2.1 业务场景业务方的目的是使用insert on duplicate key update对重复存在的记...
1005 0
MySQL 并发 insert on duplicate key 导致的死锁
|
8月前
|
关系型数据库 MySQL 数据库管理
MySQL 三个并发insert 语句导致的死锁
一 前言死锁,其实是一个很有意思也很有挑战的技术问题,大概每个DBA和部分开发同学都会在工作过程中遇见 。关于死锁我会持续写一个系列的案例分析,希望能够对想了解死锁的朋友有所帮助。本文介绍一例三个并发insert 导致的死锁,根本原因还是在于insert 唯一键申请插入意向锁这个特殊的GAP锁。其实...
1141 0
|
8月前
|
关系型数据库 MySQL 测试技术
MySQL 并发insert 导致的死锁
一 前言死锁,其实是一个很有意思也很有挑战的技术问题,大概每个DBA和部分开发同学都会在工作过程中遇见 。关于死锁我会持续写一个系列的案例分析,希望能够对想了解死锁的朋友有所帮助。二 案例分析2.1 业务场景正常的业务逻辑是用户触发行为系统发消息然后写入2条记录。如果遇到处理失败或者消息超时则会重试...
329 0
|
8月前
|
SQL 监控 关系型数据库
MySQL 并发delete不存在记录申请gap锁导致死锁
一 前言死锁,其实是一个很有意思也很有挑战的技术问题,大概每个DBA都会在工作过程中遇见。关于死锁我会持续写一个系列的案例分析,希望能够对想了解死锁的朋友有所帮助。本文源于我们的生产案例:并发申请gap锁导致的死锁案例,与之前的 死锁案例一不同,本案例是因为RR模式下两个事务中的sql可以获取同一个...
228 0