【MySQL】死锁案例之二

一 前言
    死锁,其实是一个很有意思,也很有挑战的技术问题,大概每个DBA都会在工作过程中遇见过 。关于死锁我会持续写一个系列的案例分析,希望能够对想了解死锁的朋友有所帮助。本文源于我们的生产案例:并发申请gap锁导致的死锁案例,与之前的 死锁案例一不同,本案例是因为RR模式下两个事务中的sql可以获取同一个gap锁,导致对方事务的insert 相互等待,导致死锁的。
二 案例分析
测试环境准备
Percona server 5.6.24  事务隔离级别为RR

  1. CREATE TABLE `t4` (
  2.   `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT ,
  3.   `kdt_id` int(11) unsigned NOT NULL ,
  4.   `admin_id` int(11) unsigned NOT NULL ,
  5.   `biz` varchar(20) NOT NULL DEFAULT '1' ,
  6.   `role_id` int(11) unsigned NOT NULL ,
  7.   `shop_id` int(11) unsigned NOT NULL DEFAULT '0' ,
  8.   `operator` varchar(20) NOT NULL DEFAULT '0' ,
  9.   `operator_id` int(11) NOT NULL DEFAULT '0' ,
  10.   `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  11.   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
  12.   PRIMARY KEY (`id`),
  13.   UNIQUE KEY `uniq_kid_aid_biz_rid` (`kdt_id`,`admin_id`,`role_id`,`biz`)
  14. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
  15. INSERT INTO `t4` (`id`, `kdt_id`, `admin_id`, `biz`, `role_id`, `shop_id`, `operator`, `operator_id`, `create_time`, `update_time`)
  16. VALUES
  17.  (1,10,1,'retail',1,0,'0',0,'2017-05-09 15:55:26','2017-05-09 15:55:26'),
  18.  (2,20,1,'retail',1,0,'0',0,'2017-05-09 15:55:40','2017-05-09 15:55:40'),
  19.  (3,30,1,'retail',1,0,'0',0,'2017-05-09 15:55:55','2017-05-09 15:55:55'),
  20.  (4,40,1,'retail',1,0,'0',0,'2017-05-09 15:56:06','2017-05-09 15:56:06'),
  21.  (5,50,1,'retail',1,0,'0',0,'2017-05-09 15:56:16','2017-05-09 15:56:16');

2.2 测试用例
 本测试案例场景是两个事务删除不存的行,然后在insert记录。

T2 T1
test [RW] 02:50:27 >begin;
Query OK, 0 rows affected (0.00 sec)
test [RW] 02:50:27 >begin;
Query OK, 0 rows affected (0.00 sec)
test [RW] 02:50:34 >delete from t4 where kdt_id = 15 and admin_id = 1 
and biz = 'retail' and role_id = '1';
test [RW] 02:50:41 >delete from t4 where kdt_id = 18 and admin_id = 2 and biz = 'retail' and role_id = '1';
test [RW] 02:50:43 >insert into t4(`kdt_id`, `admin_id`, `biz`, `role_id`, `shop_id`, `operator`, `operator_id`, `create_time`, `update_time`)
    -> VALUES('18', '2', 'retail', '2', '0', '0', '0', CURRENT_TIMESTAMP,CURRENT_TIMESTAMP);
test [RW] 02:51:02 >INSERT INTO t4(`kdt_id`, `admin_id`, `biz`, `role_id`, `shop_id`, `operator`, `operator_id`, `create_time`, `update_time`)
    -> VALUES ('15', '1', 'retail', '2', '0', '0', '0', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

2.3 死锁日志

  1. ------------------------
  2. LATEST DETECTED DEADLOCK
  3. ------------------------
  4. 2017-09-11 14:51:03 7f78eaf25700
  5. *** (1) TRANSACTION:
  6. TRANSACTION 462308535, ACTIVE 20 sec inserting
  7. mysql tables in use 1, locked 1
  8. LOCK WAIT 3 lock struct(s), heap size 360, 2 row lock(s), undo log entries 1
  9. MySQL thread id 3584515, OS thread handle 0x7f78ea5f5700, query id 780258123 localhost root update
  10. insert into t4(`kdt_id`, `admin_id`, `biz`, `role_id`, `shop_id`, `operator`, `operator_id`, `create_time`, `update_time`)
  11. VALUES('18', '2', 'retail', '2', '0', '0', '0', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
  12. *** (1) WAITING FOR THIS LOCK TO BE GRANTED:
  13. RECORD LOCKS space id 225 page no 4 n bits 72 index `uniq_kid_aid_biz_rid` of table `test`.`t4` trx id 462308535 lock_mode X locks gap before rec insert intention waiting
  14. *** (2) TRANSACTION:
  15. TRANSACTION 462308534, ACTIVE 29 sec inserting, thread declared inside InnoDB 5000
  16. mysql tables in use 1, locked 1
  17. 3 lock struct(s), heap size 360, 2 row lock(s), undo log entries 1
  18. MySQL thread id 3584572, OS thread handle 0x7f78eaf25700, query id 780258153 localhost root update
  19. INSERT INTO t4(`kdt_id`, `admin_id`, `biz`, `role_id`, `shop_id`, `operator`, `operator_id`, `create_time`, `update_time`)
  20. VALUES ('15', '1', 'retail', '2', '0', '0', '0', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
  21. *** (2) HOLDS THE LOCK(S):
  22. RECORD LOCKS space id 225 page no 4 n bits 72 index `uniq_kid_aid_biz_rid` of table `test`.`t4` trx id 462308534 lock_mode X locks gap before rec
  23. *** (2) WAITING FOR THIS LOCK TO BE GRANTED:
  24. RECORD LOCKS space id 225 page no 4 n bits 72 index `uniq_kid_aid_biz_rid` of table `test`.`t4` trx id 462308534 lock_mode X locks gap before rec insert intention waiting
  25. *** WE ROLL BACK TRANSACTION (2)

2.4 死锁日志分析
   首先根据《死锁案例一》 和《一个最不可思议的MySQL死锁分析》中强调 delete 不存在的记录是要加上GAP锁,事务日志中显示Lock_mode X wait .
a T2 delete from t4 where kdt_id = 15 and admin_id = 1  and biz = 'retail' and role_id = '1'; 符合条件的记录不存在,导致T2 先持有了(lock_mode X locks gap before rec) 锁住[(2,20,1,'retail',1,0)-(3,30,1,'retail',1,0)]的区间 ,防止符合条件的记录插入。
b T1的delete 于T2的delete一样 同样申请了 (lock_mode X locks gap before rec) 锁住[(2,20,1,'retail',1,0)-(3,30,1,'retail',1,0)]的区间 。

  1. It is also worth noting here that conflicting locks can be held on a gap by different transactions. For example, transaction A can hold a shared gap lock (gap S-lock) on a gap while transaction B holds an exclusive gap lock (gap X-lock) on the same gap. The reason conflicting gap locks are allowed is that if a record is purged from an index, the gap locks held on the record by different transactions must be merged.

c T1 的insert 语句申请插入意向锁,但是插入意向锁和T2持有的X GAP (lock_mode X locks gap before rec) 冲突,故等待T2中的GAP 锁释放。

  1. Gap locks in InnoDB are “purely inhibitive”, which means they only stop other transactions from inserting to the gap. They do not prevent different transactions from taking gap locks on the same gap. Thus, a gap X-lock has the same effect as a gap S-lock.

d T2 的insert 语句申请插入意向锁,但是插入意向锁和T1持有 X GAP (lock_mode X locks gap before rec) 冲突,故等待T1中的GAP 锁释放。
T1(INSERT )等待T2(DELETE),T2(INSERT)等待T1(DELETE) 故而循环等待,出现死锁。
有兴趣的读者朋友可以测试一下 delete 存在记录的场景。

2.6 如何解决呢?
   a 先select 检查一下看看是否存在,然后在删除。这里也存在两个或者多个会话并发执行同一个select where条件的,这里需要开发同学做处理。
   b insert into on deuplicate key .
三 小结
    RR事务隔离级别和GAP锁是导致死锁的常见原因,但是业务逻辑设计不合理也会出发死锁,本文的案例通过修改业务逻辑最终将死锁解决。
如果您觉得能从本文收益,可以请北在南方一瓶饮料 ^_^

时间: 2024-09-24 01:21:15

【MySQL】死锁案例之二的相关文章

【MySQL】死锁案例之一

一 前言   死锁,其实是一个很有意思,也很有挑战的技术问题,大概每个DBA和部分开发同学都会在工作过程中遇见过 .关于死锁我会持续写一个系列的案例分析,希望能够对想了解死锁的朋友有所帮助.二 案例分析2.1 环境说明MySQL 5.6 事务隔离级别为RR CREATE TABLE `ty` (   `id` int(11) NOT NULL AUTO_INCREMENT,   `a` int(11) DEFAULT NULL,   `b` int(11) DEFAULT NULL,   PRI

【MySQL】死锁案例之三

一 前言       死锁,其实是一个很有意思,也很有挑战的技术问题,大概每个DBA和部分开发朋友都会在工作过程中遇见过.关于死锁我会持续写一个系列的案例分析,希望能够对想了解死锁的朋友有所帮助.二 背景知识2.1 insert 锁机制 在分析死锁案例之前,我们先学习一下背景知识 insert 语句的加锁策略.我们先来看看官方定义: "An insert intention lock is a type of gap lock set by INSERT operations prior to

MySQL死锁的两个小案例

    最近花了些时间分析MySQL锁的内容,觉得越看越有意思. 我有个学习的习惯,有时候也不知道好还是不好,那就是喜欢直接上手练习,然后反过来练习理论.结果在学习锁的时候,感觉多多少少走了一些弯路,那就是对锁的基础的概念有一些混淆,虽然能够模拟出一些场景来,但是总是有一种隔靴搔痒的感觉,于是我就看了不少的博客,多多少少会有一些正面负面的影响,结果让我原本理解的地方又不大肯定了,所以这个时候捋一捋你学习的脉络就很重要,通过实践来得到结果,反推理论基础是好事,但是很多不明确的理解就需要通读官方文档

mysql死锁问题分析

线上某服务时不时报出如下异常(大约一天二十多次):"Deadlock found when trying to get lock;".       Oh, My God! 是死锁问题.尽管报错不多,对性能目前看来也无太大影响,但还是需要解决,保不齐哪天成为性能瓶颈.      为了更系统的分析问题,本文将从死锁检测.索引隔离级别与锁的关系.死锁成因.问题定位这五个方面来展开讨论.  图1 应用日志 1 死锁是怎么被发现的? 1.1 死锁成因&&检测方法      左图那

mysql死锁问题分析(转)

线上某服务时不时报出如下异常(大约一天二十多次):"Deadlock found when trying to get lock;".       Oh, My God! 是死锁问题.尽管报错不多,对性能目前看来也无太大影响,但还是需要解决,保不齐哪天成为性能瓶颈.     为了更系统的分析问题,本文将从死锁检测.索引隔离级别与锁的关系.死锁成因.问题定位这五个方面来展开讨论.  图1 应用日志 1 死锁是怎么被发现的? 1.1 死锁成因&&检测方法      左图那两

【JAVA秒会技术之玩转SQL】MySQL优化技术(二)

MySQL优化技术(二) [前文连接]MySQL优化技术(一) (五)常用SQL优化 1.默认情况,在使用group by 分组查询时,会先分组,其后还会默认对组内其他条件进行默认的排序,可能会降低速度.这与在查询中指定order by col1, col2类似. 如果查询中包括group by但用户想要避免排序结果的消耗,则可以使用order by null禁止排序. 例子:   2.尽量使用左连接(或右连接)来替代普通多表联查.因为使用JOIN,MySQL不需要在内存中创建临时表.    s

MySQL优化案例:半连接(semi join)优化方式导致的查询性能低下

以下是来自DBA+社群MySQL领域原创专家李海翔分享的MySQL优化案例,关于MySQL V5.6.x/5.7.x SQL查询性能问题.   专家简介   李海翔 网名:那海蓝蓝 DBA+社群MySQL领域原创专家 从事数据库研发.数据库测试与技术管理等工作10余年,对数据库的内核有深入研究,擅长于PostgreSQL和MySQL等开源数据库的内核与架构.现任职于Oracle公司MySQL全球开发团队,从事查询优化技术的研究和MySQL查询优化器的开发工作.著有<数据库查询优化器的艺术>一书

PHP将MYSQL内容读到二维数组并按指定列输出

PHP将MYSQL内容读到二维数组并按指定列输出 <? $host = "localhost";   //主机名 $user = "root";        //mysql用户名 $password = "";    //mysql密码 $database = "doc";  //mysql数据库名 $tables = "mclass";  //表名 $conn=mysql_connect(&quo

Mysql学习笔记(二)数据类型 补充

原文:Mysql学习笔记(二)数据类型 补充 PS:简单的补充一下数据类型里的String类型以及列类型... 学习内容: 1.String类型 2.列类型存储需求   String类型: i.char与varchar char与varchar的类型相似,但是他们的保存方式和检索方式不同... char的存储结构是固定长度的存储...即指定了几个字节,那么就占用几个字节,如char(4),那么无论存入的是什么字串,那么都占用四个字节...char的 可表示长度范围为0-255的任何值,当保存的字