MySQL 三个并发insert 语句导致的死锁

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 一 前言死锁,其实是一个很有意思也很有挑战的技术问题,大概每个DBA和部分开发同学都会在工作过程中遇见 。关于死锁我会持续写一个系列的案例分析,希望能够对想了解死锁的朋友有所帮助。本文介绍一例三个并发insert 导致的死锁,根本原因还是在于insert 唯一键申请插入意向锁这个特殊的GAP锁。其实...

一 前言

       死锁,其实是一个很有意思也很有挑战的技术问题,大概每个DBA和部分开发同学都会在工作过程中遇见 。关于死锁我会持续写一个系列的案例分析,希望能够对想了解死锁的朋友有所帮助。本文介绍一例三个并发insert 导致的死锁,根本原因还是在于insert 唯一键申请插入意向锁这个特殊的GAP锁。其实称呼插入意向锁 为 Insert Intention Gap Lock 更为合理。

二 案例分析

2.1 环境准备 

Percona server 5.6 RR模式

CREATE TABLE `t6` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `a` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  unique KEY `idx_a` (`a`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

insert into t6 values(1,2),(2,8),(3,9),(4,11),(5,19)

sess1

sess2

sess3

begin;

insert into t6(id,a) values(6,15);

begin;

insert into t6(id,a) values(7,15);

begin;

insert into t6(id,a) values(8,15);

rollback; 

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

2.2 死锁日志

------------------------

LATEST DETECTED DEADLOCK

------------------------
*** (1) TRANSACTION:
TRANSACTION 462308725, ACTIVE 18 sec inserting, thread declared inside InnoDB 1
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1184, 2 row lock(s), undo log entries 1
MySQL thread id 3825465, OS thread handle 0x7f78eaef4700, query id 781148519 localhost root update
insert into t6(id,a) values(7,15)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308725 lock_mode X insert intention waiting
*** (2) TRANSACTION:
TRANSACTION 462308726, ACTIVE 10 sec inserting, thread declared inside InnoDB 1
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1184, 2 row lock(s), undo log entries 1
MySQL thread id 3825581, OS thread handle 0x7f78eae30700, query id 781148528 localhost root update
insert into t6(id,a) values(8,15)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308726 lock mode S
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308726 lock_mode X insert intention waiting
*** WE ROLL BACK TRANSACTION (2)

2.3 死锁分析

首先依然要再次强调insert 插入操作的加锁逻辑。 第一阶段: 唯一性约束检查,先申请LOCK_S + LOCK_ORDINARY 第二阶段: 获取阶段一的锁并且insert成功之后,插入的位置有Gap锁:LOCK_INSERT_INTENTION,为了防止其他insert唯一键冲突。 新数据插入:LOCK_X + LOCK_REC_NOT_GAP 对于insert操作来说,若发生唯一约束冲突,则需要对冲突的唯一索引加上S Next-key Lock。从这里会发现,即使是RC事务隔离级别,也同样会存在Next-Key Lock锁,从而阻塞并发。然而,文档没有说明的是,对于检测到冲突的唯一索引,等待线程在获得S Lock之后,还需要对下一个记录进行加锁,在源码中由函数row_ins_scan_sec_index_for_duplicate进行判断. 其次 我们需要了解锁的兼容性矩阵

从兼容性矩阵我们可以得到如下结论:

INSERT操作之间不会有冲突。

GAP,Next-Key会阻止Insert。

GAP和Record,Next-Key不会冲突

Record和Record、Next-Key之间相互冲突。

已有的Insert锁不阻止任何准备加的锁。

这个案例是三个会话并发执行的,我打算一步一步来分析每个步骤执行完之后的事务日志。 第一步 sess1 执行插入操作 insert into t6(id,a) values(6,15);

---TRANSACTION 462308737, ACTIVE 5 sec
1 lock struct(s), heap size 360, 0 row lock(s), undo log entries 1
MySQL thread id 3825779, OS thread handle 0x7f78eacd9700, query id 781149440 localhost root init
show engine innodb status
TABLE LOCK table `test`.`t6` trx id 462308737 lock mode IX

因为第一个插入的语句,所以唯一性冲突检查通过,成功插入(6,15). 此时sess1 会话持有(6,15)的LOCK_X|LOCK_REC_NOT_GAP锁。参考"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." 第二步 sess2 执行插入操作 insert into t6(id,a) values(7,15);

---TRANSACTION 462308738, ACTIVE 4 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1
MySQL thread id 3825768, OS thread handle 0x7f78ea9c9700, query id 781149521 localhost root update
insert into t6(id,a) values(7,15)
------- TRX HAS BEEN WAITING 4 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S waiting
------------------
TABLE LOCK table `test`.`t6` trx id 462308738 lock mode IX
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S waiting
---TRANSACTION 462308737, ACTIVE 66 sec
2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1
MySQL thread id 3825779, OS thread handle 0x7f78eacd9700, query id 781149526 localhost root init
show engine innodb status
TABLE LOCK table `test`.`t6` trx id 462308737 lock mode IX
RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308737 lock_mode X locks rec but not gap

首先sess2的insert 申请了IX锁,因为sess1 会话已经插入成功并且持有唯一键 a=15的X 行锁 ,故而sess2 insert 进行唯一性检查,先申请LOCK_S + LOCK_ORDINARY ,事务日志列表中提示lock mode S waiting 第三部 sess3 执行插入操作 insert into t6(id,a) values(8,15);

---TRANSACTION 462308739, ACTIVE 3 sec inserting

mysql tables in use 1, locked 1

LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1

MySQL thread id 3825764, OS thread handle 0x7f78ea593700, query id 781149555 localhost root update

insert into t6(id,a) values(8,15)

------- TRX HAS BEEN WAITING 3 SEC FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308739 lock mode S waiting

------------------

TABLE LOCK table `test`.`t6` trx id 462308739 lock mode IX

RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308739 lock mode S waiting

---TRANSACTION 462308738, ACTIVE 35 sec inserting

mysql tables in use 1, locked 1

LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1

MySQL thread id 3825768, OS thread handle 0x7f78ea9c9700, query id 781149521 localhost root update

insert into t6(id,a) values(7,15)

------- TRX HAS BEEN WAITING 35 SEC FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S waiting

------------------

TABLE LOCK table `test`.`t6` trx id 462308738 lock mode IX

RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S waiting

---TRANSACTION 462308737, ACTIVE 97 sec

2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1

MySQL thread id 3825779, OS thread handle 0x7f78eacd9700, query id 781149560 localhost root init

show engine innodb status

TABLE LOCK table `test`.`t6` trx id 462308737 lock mode IX

RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308737 lock_mode X locks rec but not gap、

与会话sess2 的加锁申请流程一致,都在等待sess1释放锁资源。 第四步 sess1 执行回滚操作,sess2 不提交

sess1 rollback; 此时sess2 插入成功,sess3出现死锁,此时sess2 insert插入成功,还未提交,事务列表如下:

Trx id counter 462308744

Purge done for trx s n:o < 462308744 undo n:o < 0 state: running but idle

History list length 1866

LIST OF TRANSACTIONS FOR EACH SESSION:

---TRANSACTION 462308737, not started

MySQL thread id 3825779, OS thread handle 0x7f78eacd9700, query id 781149626 localhost root init

show engine innodb status

---TRANSACTION 462308739, not started

MySQL thread id 3825764, OS thread handle 0x7f78ea593700, query id 781149555 localhost root cleaning up

---TRANSACTION 462308738, ACTIVE 75 sec

5 lock struct(s), heap size 1184, 3 row lock(s), undo log entries 1

MySQL thread id 3825768, OS thread handle 0x7f78eadce700, query id 781149608 localhost root cleaning up

TABLE LOCK table `test`.`t6` trx id 462308738 lock mode IX

RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S

RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S

RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock_mode X insert intention

RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S locks gap before rec

死锁的原因 sess1 insert成功并针对a=15的唯一键加上X锁。 sess2 执行insert 插入(6,15), 在插入之前进行唯一性检查发现和sess1的已经插入的记录重复键需要申请LOCK_S|LOCK_ORDINARY, 但与sess1 的(LOCK_X | LOCK_REC_NOT_GAP)冲突,加入等待队列,等待sess1 释放锁。 sess3 执行insert 插入(7,15), 在插入之前进行唯一性检查发现和sess1的已经插入的记录重复键需要申请LOCK_S|LOCK_ORDINARY, 但与sess1 的(LOCK_X | LOCK_REC_NOT_GAP)冲突,加入等待队列,等待sess1 释放锁。  sess1 执行rollback, sess1 释放索引a=15 上的排他记录锁(LOCK_X | LOCK_REC_NOT_GAP),此后 sess2和sess3 获得S锁(LOCK_S|LOCK_ORDINARY)成功,sess2和sess3都要请求索引a=15上的排他记录锁(LOCK_X | LOCK_REC_NOT_GAP),日志中提示 lock_mode X insert intention。由于X锁与S锁互斥,sess2和sess3都等待对方释放S锁,于是出现死锁,MySQL 选择回滚其中之一。

三 总结

    死锁分析是已经很有挑战的事情,尤其对于insert 唯一键冲突,要分多个阶段去申请,也要理解锁的兼容矩阵。对于这块我还有需要在学习了解的知识点,本文算是抛砖引玉,如有分析理解不正确的地方,望大家指正。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
28天前
|
SQL 关系型数据库 MySQL
遇到mysql数据库死锁,你会怎么排查?
遇到mysql数据库死锁,你会怎么排查?
73 0
|
6天前
|
关系型数据库 MySQL 数据库
MySQL高级篇——MVCC多版本并发控制
什么是MVCC、快照读与当前读、隐藏字段、Undo Log版本链、ReadView、举例说明、InnoDB 解决幻读问题
MySQL高级篇——MVCC多版本并发控制
|
1月前
|
SQL 关系型数据库 MySQL
MySQL实现并发控制的过程
数据库系统到底是怎么进行并发访问控制的?本文以 MySQL 8.0.35 代码为例,尝试对 MySQL 中的并发访问控制进行整体介绍。
|
1月前
|
存储 自然语言处理 关系型数据库
MySQL全文索引源码剖析之Insert语句执行过程
【8月更文挑战第17天】在MySQL中,处理含全文索引的`INSERT`语句涉及多步骤。首先进行语法解析确认语句结构无误;接着语义分析检查数据是否符合表结构及约束。随后存储引擎执行插入操作,若涉及全文索引则进行分词处理,并更新倒排索引结构。此外,事务管理确保了操作的完整性和一致性。通过示例创建含全文索引的表并插入数据,可见MySQL如何高效地处理此类操作,有助于优化数据库性能和提升全文搜索效果。
|
1月前
|
存储 SQL 关系型数据库
深入MySQL锁机制:原理、死锁解决及Java防范技巧
深入MySQL锁机制:原理、死锁解决及Java防范技巧
|
1月前
|
SQL JavaScript 关系型数据库
Mysql索引不当引发死锁问题
本文通过真实案例解析了MySQL在高并发环境下出现死锁的问题。数据库表`t_award`包含多个索引,但在执行特定SQL语句时遭遇索引失效,导致更新操作变慢并引发死锁。分析发现,联合索引`(pool_id, identifier, status, is_redeemed)`因`identifier`允许为空值而导致索引部分失效。此外,`pool_id`上的普通索引产生的间隙锁在高并发下加剧了死锁风险。为解决此问题,文中提出了调整索引顺序至`(pool_id, status, is_redeemed, identifier)`等方案来优化索引使用,进而减轻死锁现象。
|
1月前
|
Oracle 关系型数据库 MySQL
Mysql和Oracle数据库死锁查看以及解决
【8月更文挑战第11天】本文介绍了解决MySQL与Oracle数据库死锁的方法。MySQL可通过`SHOW ENGINE INNODB STATUS`查看死锁详情,并自动回滚一个事务解除死锁;也可手动KILL事务。Oracle则通过查询V$LOCK与V$SESSION视图定位死锁,并用`ALTER SYSTEM KILL SESSION`命令终止相关会话。预防措施包括遵循ACID原则、优化索引及拆分大型事务。
|
1月前
|
关系型数据库 MySQL
解决MySQL insert出现Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘xxx‘ at row 1
解决MySQL insert出现Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘xxx‘ at row 1
53 2
|
1月前
|
存储 关系型数据库 MySQL
在 MySQL 中使用 Insert Into Select
【8月更文挑战第11天】
90 0
在 MySQL 中使用 Insert Into Select
|
25天前
|
监控 关系型数据库 MySQL
MySQL死锁是什么
【8月更文挑战第26天】MySQL死锁是指两个或多个事务在执行过程中,因争夺锁资源而造成的相互等待的现象,若无外力干涉,它们都将无法继续执行。这种相互等待的情况会导致整个系统陷入停滞状态,影响数据库的性能和稳定性。
37 0

热门文章

最新文章