InnoDB的锁机制浅析(五)—死锁场景(Insert死锁)
可能的死锁场景
文章总共分为五个部分:
- InnoDB的锁机制浅析(一)—基本概念/兼容矩阵
- InnoDB的锁机制浅析(二)—探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/插入意向锁)
- InnoDB的锁机制浅析(三)—幻读
- InnoDB的锁机制浅析(四)—不同SQL的加锁状况
- InnoDB的锁机制浅析(五)—死锁场景(Insert死锁)
大而全版(五合一):InnoDB的锁机制浅析(All in One)
前言
这一章节只列举两种死锁场景,其他的死锁问题大多也万变不离其宗。
示例的基础是一个只有两列的数据库表。
mysql> CREATE TABLE test (
id int(11) NOT NULL,
code int(11) NOT NULL,
PRIMARY KEY(id),
KEY (code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
mysql> INSERT INTO test(id,code) values(1,1),(10,10);
锁的兼容矩阵如下:
| --- | 排它锁(X) | 意向排它锁(IX) | 共享锁(S) | 意向共享锁(IS) |
|---|---|---|---|---|
| 排它锁(X) | N | N | N | N |
| 意向排它锁(IX) | N | OK | N | OK |
| 共享锁(S) | N | N | OK | OK |
| 意向共享锁(IS) | N | OK | OK | OK |
1 Duplicate key error引发的死锁
并发条件下,唯一键索引冲突可能会导致死锁,这种死锁一般分为两种,一种是rollback引发,另一种是commit引发。
1.1 rollback引发的Duplicate key死锁
我命名为insert-insert-insert-rollback死锁
| 事务一 | 事务二 | 事务三 |
|---|---|---|
| mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into test values (2,2); Query OK, 1 row affected (0.01 sec) |
||
| mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into test values (2,2); 执行之后被阻塞,等待事务一 |
||
| mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into test values (2,2); 执行之后被阻塞,等待事务一 |
||
| mysql>rollback; Query OK, 0 rows affected (0.00 sec) |
||
| ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction | ||
| Query OK, 1 row affected (16.13 sec) |
当事务一执行回滚时,事务二和事务三发生了死锁。InnoDB的死锁检测一旦检测到死锁发生,会自动失败其中一个事务,因此看到的结果是一个失败另一个成功。
为什么会死锁?
死锁产生的原因是事务一插入记录时,对(2,2)记录加X锁,此时事务二和事务三插入数据时检测到了重复键错误,此时事务二和事务三要在这条索引记录上设置S锁,由于X锁的存在,S锁的获取被阻塞。
事务一回滚,由于S锁和S锁是可以兼容的,因此事务二和事务三都获得了这条记录的S锁,此时其中一个事务希望插入,则该事务期望在这条记录上加上X锁,然而另一个事务持有S锁,S锁和X锁互相是不兼容的,两个事务就开始互相等待对方的锁释放,造成了死锁。
事务二和事务三为什么会加S锁,而不是直接等待X锁
事务一的insert语句加的是隐式锁(隐式的Record锁、X锁),但是其他事务插入同一行记录时,出现了唯一键冲突,事务一的隐式锁升级为显示锁。
事务二和事务三在插入之前判断到了唯一键冲突,是因为插入前的重复索引检查,这次检查必须进行一次当前读,于是非唯一索引就会被加上S模式的next-key锁,唯一索引就被加上了S模式的Record锁。
因为插入和更新之前都要进行重复索引检查而执行当前读操作,所以RR隔离级别下,同一个事务内不连续的查询,可能也会出现幻读的效果(但个人并不认为RR级别下也会出现幻读,幻读的定义应该是连续的读取)。而连续的查询由于都是读取快照,中间没有当前读的操作,所以不会出现幻读。
1.2 commit引发的Duplicate key死锁
delete-insert-insert-commit死锁
| 事务一 | 事务二 | 事务三 |
|---|---|---|
| mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> delete from test where id=2; Query OK, 1 row affected (0.01 sec) |
||
| mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into test values (2,2); 执行之后被阻塞,等待事务一 |
||
| mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into test values (2,2); 执行之后被阻塞,等待事务一 |
||
| mysql>commit; Query OK, 0 rows affected (0.00 sec) |
||
| ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction | ||
| Query OK, 1 row affected (2.37 sec) |
这种情况下产生的死锁和insert-insert-insert-rollback死锁产生的原理一致。
6.2 数据插入的过程
经过以上分析,一条数据在插入时经过以下几个过程:
假设数据表test.test中存在(1,1)、(5,5)和(10,10)三条记录。
- 事务开启,尝试获取插入意向锁。例如,事务一执行了
select * from test where id>8 for update,事务二要插入(9,9),此时先要获取插入意向锁,由于事务一已经在对应的记录和间隙上加了X锁,因此事务二被阻塞,并且阻塞的原因是获取插入意向锁时被事务一的X锁阻塞。 - 获取意向锁之后,插入之前进行重复索引检查。重复索引检查为当前读,需要添加S锁。
- 如果是已经存在唯一索引,且索引未加锁。直接抛出
Duplicate key的错误。如果存在唯一索引,且索引加锁,等待锁释放。 - 重复检查通过之后,加入X锁,插入记录
3 GAP与Insert Intention冲突引发死锁
update-insert死锁
仍然是表test,当前表中的记录如下:
mysql> select * from test;
+----+------+
| id | code |
+----+------+
| 1 | 1 |
| 5 | 5 |
| 10 | 10 |
+----+------+
3 rows in set (0.01 sec)
| 事务一 | 事务二 |
|---|---|
| begin; | begin; |
| select * from test where id=5 for update; | select * from test where id=10 for update; |
| insert into test values(7,7); | |
| insert into test values(7,7); | |
| Query OK, 1 row affected (5.03 sec) | |
| ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction |
使用show engine innodb status查看死锁状态。先后出现lock_mode X locks gap before rec insert intention waiting和lock_mode X locks gap before rec字眼,是gap锁和插入意向锁的冲突导致的死锁。
回顾select...for update的加锁范围
首先回顾一下两个事务中的select ... for update做了哪些加锁操作。
code=5时,首先会获取code=5的索引记录锁(Record锁),根据之前gap锁的介绍,会在前一个索引和当前索引之间的间隙加锁,于是区间(1,5)之间被加上了X模式的gap锁。除此之外RR模式下,还会加next-key锁,于是区间(5,10]被加了next-key锁;
- 因此,
code=5的加锁范围是,区间(1,5)的gap锁,{5}索引Record锁,(5,10]的next-key锁。即区间(1,10)上都被加上了X模式的锁。 - 同理,
code=10的加锁范围是,区间(5,10)的gap锁,{10}索引Record锁,(10,+∞)的next-key锁。
由gap锁的特性,兼容矩阵中冲突的锁也可以被不同的事务同时加在一个间隙上。上述两个select ... for update语句出现了间隙锁的交集,code=5的next-key锁和code=10的gap锁有重叠的区域——(5,10)。
死锁的成因
当事务一执行插入语句时,会先加X模式的插入意向锁,即兼容矩阵中的IX锁。
但是由于插入意向锁要锁定的位置存在X模式的gap锁。兼容矩阵中IX和X锁是不兼容的,因此事务一的IX锁会等待事务二的gap锁释放。
事务二也执行插入语句,与事务一同样,事务二的插入意向锁IX锁会等待事务一的gap锁释放。
两个事务互相等待对方先释放锁,因此出现死锁。
2 总结
除了以上给出的几种死锁模式,还有很多其他死锁的场景。
无论是哪种场景,万变不离其宗,都是由于某个区间上或者某一个记录上可以同时持有锁,例如不同事务在同一个间隙gap上的锁不冲突;不同事务中,S锁可以阻塞X锁的获取,但是不会阻塞另一个事务获取该S锁。这样才会出现两个事务同时持有锁,并互相等待,最终导致死锁。
其中需要注意的点是,增、删、改的操作都会进行一次当前读操作,以此获取最新版本的数据,并检测是否有重复的索引。
这个过程除了会导致RR隔离级别下出现死锁之外还会导致其他两个问题:
- 第一个是可重复读可能会因为这次的当前读操作而中断,(同样,幻读可能也会因此产生);
- 第二个是其他事务的更新可能会丢失(解决方式:悲观锁、乐观锁)。
InnoDB的锁机制浅析(五)—死锁场景(Insert死锁)的更多相关文章
- InnoDB的锁机制浅析(四)—不同SQL的加锁状况
不同SQL的加锁状况 文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/ ...
- InnoDB的锁机制浅析(三)—幻读
文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/插入意向锁) Inno ...
- InnoDB的锁机制浅析(二)—探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/插入意向锁)
Record锁/Gap锁/Next-key锁/插入意向锁 文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Recor ...
- InnoDB的锁机制浅析(一)—基本概念/兼容矩阵
InnoDB锁的基本概念 文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key ...
- InnoDB的锁机制浅析(All in One)
目录 InnoDB的锁机制浅析 1. 前言 2. 锁基本概念 2.1 共享锁和排它锁 2.2 意向锁-Intention Locks 2.3 锁的兼容性 3. InnoDB中的锁 3.1 准备工作 3 ...
- 巧用MySQL InnoDB引擎锁机制解决死锁问题(转)
该文会通过一个实际例子中的死锁问题的解决过程,进一步解释innodb的行锁机制 最近,在项目开发过程中,碰到了数据库死锁问题,在解决问题的过程中,笔者对MySQL InnoDB引擎锁机制的理解逐步加深 ...
- InnoDB之锁机制
前两天听了姜老大关于InnoDB中锁的相关培训,刚好也在看这方面的知识,就顺便利用时间把这部分知识做个整理,方便自己理解.主要分为下面几个部分 1. InnoDB同步机制 InnoDB存储引擎有两种同 ...
- 从一个死锁看mysql innodb的锁机制
背景及现象 线上生产环境在某些时候经常性的出现数据库操作死锁,导致业务人员无法进行操作.经过DBA的分析,是某一张表的insert操 作和delete操作发生了死锁.简单介绍下数据库的情况(因为涉及到 ...
- MySQL锁机制浅析
MySQL使用了3种锁机制 行级锁,开销大,加锁慢,会出现死锁,发生锁冲突的概率最高,并发度也最高 表级锁,开销小,加锁快,不会出现死锁,发生锁冲突的概率最低,并发度最低 页级锁,开销和加锁时间界于表 ...
随机推荐
- 质数——6N±1法
6N±1法求素数 任何一个自然数,总可以表示成为如下的形式之一: 6N,6N+1,6N+2,6N+3,6N+4,6N+5 (N=0,1,2,…) 显然,当N≥1时,6N,6N+2,6N+3,6N+4都 ...
- 小议 开源中国 I LOVE YOU js代码
今天在开源中国看到一篇神作<I LOVE YOU js代码>是17号的文章了,也许你已经看过了. 文章非常有意思,由 5 个 "爱心" 组成的一段js代码,能正常执行, ...
- [原]Android开发优化-Adapter优化
ListView作为Android开发中使用频率最高的一个控件,保证ListView的流畅运行,对用户体验的提高至关重要.Adapter是ListView和数据源之间的中间人,当每条数据进入可见区时, ...
- 用python处理文本,本地文件系统以及使用数据库的知识基础
主要是想通过python之流的脚本语言来进行文件系统的遍历,处理文本以及使用简易数据库的操作. 本文基于陈皓的:<程序员技术练级攻略> 一.Python csv 对于电子表格和数据库导出文 ...
- Tensorflow数据读取的方式
深度学习既然是基于数据的方法,先不管多抽象,那总归是有读取数据的方法的吧,这里的数据应该是一个统称,包含我们讲的数据集和变量tensor. tf读取数据一共有3种方法: 供给数据(Feeding): ...
- Linux释放内存小脚本
最近发现渣渣ECS内存总是不够用,内存太小一不小心就用完了,用完就用完吧,内存用来做cache是可以快一些,但是内存用完了老是一顿一顿的卡,实在有点受不了,于是就写了释放内存的小脚本,觉得卡了就释放下 ...
- 月薪20K软件测试自动化岗必问面试题:验证码识别与处理
本文乃Happy老师的得意门生来自java全栈自动化测试4期的小核桃所作.正所谓严师出高徒,笔下有黄金~~让我们一起来征服面试官吧~~ 在做自动化测试的时候,经常会遇到需要输入验证码的地方,有些可以让 ...
- python3操作数据库 借助pycharm快速连接并操作mysql数据库
1.https://blog.csdn.net/qiushi_1990/article/details/78041299
- JDK1.8源码hashMap
一.概念 允许key为null,value为null:线程不安全:不保证映射的顺序:迭代 collection 视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数) ...
- mysql grep database error(cannot rmdir /dbname)
service mysql stop cd /var/lib/mysql/dbname rm -rf .fmr rm -rf .txt service mysql start srop databas ...