可能的死锁场景

文章总共分为五个部分:

大而全版(五合一):InnoDB的锁机制浅析(All in One)

前言

这一章节只列举两种死锁场景,其他的死锁问题大多也万变不离其宗。

示例的基础是一个只有两列的数据库表。

  1. mysql> CREATE TABLE test (
  2. id int(11) NOT NULL,
  3. code int(11) NOT NULL,
  4. PRIMARY KEY(id),
  5. KEY (code)
  6. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  7. 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,当前表中的记录如下:

  1. mysql> select * from test;
  2. +----+------+
  3. | id | code |
  4. +----+------+
  5. | 1 | 1 |
  6. | 5 | 5 |
  7. | 10 | 10 |
  8. +----+------+
  9. 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. JMS学习(六)--提高非持久订阅者的可靠性 以及 订阅恢复策略

    一,非持久订阅者 和 实时消费消息 在这篇文章中区分了Domain为Pub/Sub.Destination为Topic时,消费者有两种:持久订阅者 和 非持久订阅者. 对于持久订阅者而言,只要订阅了某 ...

  2. chrome 隐藏技能之 base64 图片转换

    有时候我们要转换图片为base64,或者将base64转回图片,可能都需要找一些在线工具或者软件类型的工具才行.当然 chrome 也算是软件,但是好在做前端的都有 chrome.好了,来看下简单的例 ...

  3. C根据排序字符串

    #include<stdio.h> #include<string.h> #include <stdlib.h> #define STR_LEN_MAX 100 c ...

  4. 在OS X 10.9配置WebDAV服务器联合NSURLSessionUploa…

    CHENYILONG Blog   在OS X 10.9配置WebDAV服务器联合NSURLSessionUploadTask实现文件上传iOS7推出的NSURLSession简化了NSURLConn ...

  5. js基础知识:闭包,事件处理,原型

    闭包:其实就是js代码在执行的时候会创建变量对象的一个作用域链,标识符解析的时候会沿着作用域链一级一级的网上搜索,最后到达全局变量停止.所以某个函数可以访问外层的局部变量和全局变量,但是访问不了里层的 ...

  6. Thinkpad X220 升级 Windows 10 后无线网卡消失问题

    11年购买的Thinkpad X220从Win7升级到Win10后,用着还是挺顺手的,网络显示等一切正常,直到今天合上盖子电脑睡眠以后再次打开,wifi消失不见.重启,关机再开机,都没用,只显示有线网 ...

  7. CentOS 无法通过 yum 安装新版 nodejs 解决办法(安装的还是老版的)

    官网安装说明:CentOS 安装 nodejs 第一步: curl --silent --location https://rpm.nodesource.com/setup_10.x | sudo b ...

  8. How to become a successful bug bounty hunter

    出处:https://www.hackerone.com/blog/become-a-successful-bug-bounty-hunter 如果你梦想成为赏金猎人,你的梦想就会成真 - 不要把你的 ...

  9. linux 串口驱动(二)初始化 【转】

    转自:http://blog.chinaunix.net/uid-27717694-id-3493611.html 8250串口的初始化: (1)定义uart_driver.uart_ops.uart ...

  10. Windows下安装Python及Eclipse中配置PyDev插件

    最近开始接触Python,鉴于之前安装Java的教训,决定这次边安装Python,边写下历程,供日后反复使用. 在Python官网http://www.python.org/下载Python版本,鉴于 ...