可能的死锁场景

文章总共分为五个部分:

大而全版(五合一):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 waitinglock_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死锁)的更多相关文章

  1. InnoDB的锁机制浅析(四)—不同SQL的加锁状况

    不同SQL的加锁状况 文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/ ...

  2. InnoDB的锁机制浅析(三)—幻读

    文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/插入意向锁) Inno ...

  3. InnoDB的锁机制浅析(二)—探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/插入意向锁)

    Record锁/Gap锁/Next-key锁/插入意向锁 文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Recor ...

  4. InnoDB的锁机制浅析(一)—基本概念/兼容矩阵

    InnoDB锁的基本概念 文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Record锁/Gap锁/Next-key ...

  5. InnoDB的锁机制浅析(All in One)

    目录 InnoDB的锁机制浅析 1. 前言 2. 锁基本概念 2.1 共享锁和排它锁 2.2 意向锁-Intention Locks 2.3 锁的兼容性 3. InnoDB中的锁 3.1 准备工作 3 ...

  6. 巧用MySQL InnoDB引擎锁机制解决死锁问题(转)

    该文会通过一个实际例子中的死锁问题的解决过程,进一步解释innodb的行锁机制 最近,在项目开发过程中,碰到了数据库死锁问题,在解决问题的过程中,笔者对MySQL InnoDB引擎锁机制的理解逐步加深 ...

  7. InnoDB之锁机制

    前两天听了姜老大关于InnoDB中锁的相关培训,刚好也在看这方面的知识,就顺便利用时间把这部分知识做个整理,方便自己理解.主要分为下面几个部分 1. InnoDB同步机制 InnoDB存储引擎有两种同 ...

  8. 从一个死锁看mysql innodb的锁机制

    背景及现象 线上生产环境在某些时候经常性的出现数据库操作死锁,导致业务人员无法进行操作.经过DBA的分析,是某一张表的insert操 作和delete操作发生了死锁.简单介绍下数据库的情况(因为涉及到 ...

  9. MySQL锁机制浅析

    MySQL使用了3种锁机制 行级锁,开销大,加锁慢,会出现死锁,发生锁冲突的概率最高,并发度也最高 表级锁,开销小,加锁快,不会出现死锁,发生锁冲突的概率最低,并发度最低 页级锁,开销和加锁时间界于表 ...

随机推荐

  1. 质数——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都 ...

  2. 小议 开源中国 I LOVE YOU js代码

    今天在开源中国看到一篇神作<I LOVE YOU js代码>是17号的文章了,也许你已经看过了. 文章非常有意思,由 5 个 "爱心" 组成的一段js代码,能正常执行, ...

  3. [原]Android开发优化-Adapter优化

    ListView作为Android开发中使用频率最高的一个控件,保证ListView的流畅运行,对用户体验的提高至关重要.Adapter是ListView和数据源之间的中间人,当每条数据进入可见区时, ...

  4. 用python处理文本,本地文件系统以及使用数据库的知识基础

    主要是想通过python之流的脚本语言来进行文件系统的遍历,处理文本以及使用简易数据库的操作. 本文基于陈皓的:<程序员技术练级攻略> 一.Python csv 对于电子表格和数据库导出文 ...

  5. Tensorflow数据读取的方式

    深度学习既然是基于数据的方法,先不管多抽象,那总归是有读取数据的方法的吧,这里的数据应该是一个统称,包含我们讲的数据集和变量tensor. tf读取数据一共有3种方法: 供给数据(Feeding): ...

  6. Linux释放内存小脚本

    最近发现渣渣ECS内存总是不够用,内存太小一不小心就用完了,用完就用完吧,内存用来做cache是可以快一些,但是内存用完了老是一顿一顿的卡,实在有点受不了,于是就写了释放内存的小脚本,觉得卡了就释放下 ...

  7. 月薪20K软件测试自动化岗必问面试题:验证码识别与处理

    本文乃Happy老师的得意门生来自java全栈自动化测试4期的小核桃所作.正所谓严师出高徒,笔下有黄金~~让我们一起来征服面试官吧~~ 在做自动化测试的时候,经常会遇到需要输入验证码的地方,有些可以让 ...

  8. python3操作数据库 借助pycharm快速连接并操作mysql数据库

    1.https://blog.csdn.net/qiushi_1990/article/details/78041299

  9. JDK1.8源码hashMap

    一.概念 允许key为null,value为null:线程不安全:不保证映射的顺序:迭代 collection 视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数) ...

  10. 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 ...