【MySQL】死锁案例之一

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

  1. CREATE TABLE `ty` (
  2.   `id` int(11) NOT NULL AUTO_INCREMENT,
  3.   `a` int(11) DEFAULT NULL,
  4.   `b` int(11) DEFAULT NULL,
  5.   PRIMARY KEY (`id`),
  6.   KEY `idxa` (`a`)
  7. ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4
  8. insert into ty(a,b) values(2,3),(5,4),(6,7);

2.2 测试用例


T2


T1


begin;


delete from  ty where  a=5;


begin;


delete from  ty where  a=5;


insert into ty(a,b) values(2,10);


delete from  ty where  a=5;

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

                                                            
2.3 死锁日志

  1. ------------------------
  2. LATEST DETECTED DEADLOCK
  3. ------------------------
  4. 2017-09-09 22:34:13 7f78eab82700
  5. *** (1) TRANSACTION:
  6. TRANSACTION 462308399, ACTIVE 33 sec starting index read
  7. mysql tables in use 1, locked 1
  8. LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s)
  9. MySQL thread id 3525577, OS thread handle 0x7f896cc4b700, query id 780039657 localhost root updating
  10. delete from ty where a=5
  11. *** (1) WAITING FOR THIS LOCK TO BE GRANTED:
  12. RECORD LOCKS space id 219 page no 4 n bits 72 index `idxa` of table `test`.`ty` trx id 462308399 lock_mode X waiting
  13. *** (2) TRANSACTION:
  14. TRANSACTION 462308398, ACTIVE 61 sec inserting, thread declared inside InnoDB 5000
  15. mysql tables in use 1, locked 1
  16. 5 lock struct(s), heap size 1184, 4 row lock(s), undo log entries 2
  17. MySQL thread id 3525490, OS thread handle 0x7f78eab82700, query id 780039714 localhost root update
  18. insert into ty(a,b) values(2,10)
  19. *** (2) HOLDS THE LOCK(S):
  20. RECORD LOCKS space id 219 page no 4 n bits 72 index `idxa` of table `test`.`ty` trx id 462308398 lock_mode X
  21. *** (2) WAITING FOR THIS LOCK TO BE GRANTED:
  22. RECORD LOCKS space id 219 page no 4 n bits 72 index `idxa` of table `test`.`ty` trx id 462308398 lock_mode X locks gap before rec insert intention waiting
  23. *** WE ROLL BACK TRANSACTION (1)

2.3分析死锁日志
首先要理解的是 对同一个字段申请加锁是需要排队. S GAP  于
其次表ty中a为普通索引字段,我们根据事务执行的时间顺序来解释,这样比较好理解。
a 根据死锁日志显示 事务2 也即sess1执行的事务,根据 HOLDS THE LOCK(S)显示
   sess1 先执行 delete from ty where a=5 ,该事务持有索引a=5 的行锁lock_mode X ,因为是RR隔离级别,所以sess1 还持有两个gap锁[1,2]-[2,5], [2,5]-[3,6] 。
b 事务1的日志也即sess2执行的事务,申请对 a=5 加锁,一个rec lock 和两个gap锁,因为sess1中delete还没释放,故sess2的事务1等待sess1的事务2释放a=5的锁资源。
c 然后根据WAITING FOR THIS LOCK TO BE GRANTED,提示事务2 insert语句正在等待 lock_mode X locks gap before rec insert intention waiting,
因为insert语句 [4,2] 介于gap锁[1,2]-[2,5]之间,所以有了提示 "lock_mode X locks gap",insert语句必须等待前面 sess2中delete 获取锁并且释放锁。于是,sess2(delete) 等待sess1(delete) ,sess1(insert)等待sess2(delete),循环等待,造成死锁。
问题 如果sess1 执行 insert into ty(a,b) values(5,10); sess2会遇到死锁吗?

三 案例二
3.1 索引为唯一键
MySQL 5.6 事务隔离级别为RR

  1. CREATE TABLE `t2` (
  2.   `id` int(11) NOT NULL AUTO_INCREMENT,
  3.   `a` int(11) DEFAULT NULL,
  4.   `b` int(11) DEFAULT NULL,
  5.   PRIMARY KEY (`id`),
  6.   unique KEY `idxa` (`a`)
  7. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
  8. insert into t2(a,b) values(2,3),(5,4),(6,7)

3.2 测试用例


T2


T1


begin;


delete from  ty where  a=5;


begin;


delete from  ty where  a=5;


insert into ty(a,b) values(2,10);


delete from  ty where  a=5;

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

3.3 死锁日志

  1. ------------------------
  2. LATEST DETECTED DEADLOCK
  3. ------------------------
  4. 2017-09-10 00:03:31 7f78ea936700
  5. *** (1) TRANSACTION:
  6. TRANSACTION 462308445, ACTIVE 9 sec starting index read
  7. mysql tables in use 1, locked 1
  8. LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s)
  9. MySQL thread id 3526009, OS thread handle 0x7f896cc4b700, query id 780047877 localhost root updating
  10. delete from t2 where a=5
  11. *** (1) WAITING FOR THIS LOCK TO BE GRANTED:
  12. RECORD LOCKS space id 221 page no 4 n bits 72 index `idxa` of table `test`.`t2` trx id 462308445 lock_mode X waiting
  13. *** (2) TRANSACTION:
  14. TRANSACTION 462308444, ACTIVE 17 sec inserting, thread declared inside InnoDB 5000
  15. mysql tables in use 1, locked 1
  16. 4 lock struct(s), heap size 1184, 3 row lock(s), undo log entries 2
  17. MySQL thread id 3526051, OS thread handle 0x7f78ea936700, query id 780047890 localhost root update
  18. insert t2(a,b) values(5,10)
  19. *** (2) HOLDS THE LOCK(S):
  20. RECORD LOCKS space id 221 page no 4 n bits 72 index `idxa` of table `test`.`t2` trx id 462308444 lock_mode X locks rec but not gap
  21. *** (2) WAITING FOR THIS LOCK TO BE GRANTED:
  22. RECORD LOCKS space id 221 page no 4 n bits 72 index `idxa` of table `test`.`t2` trx id 462308444 lock mode S waiting
  23. *** WE ROLL BACK TRANSACTION (1)

3.4 分析死锁日志
首先我们要特别说明delete的加锁逻辑



a 找到满足条件的记录,并且记录有效,则对记录加X锁,No Gap锁(lock_mode X locks rec but not gap);

b 找到满足条件的记录,但是记录无效(标识为删除的记录),则对记录加next key锁(同时锁住记录本身,以及记录之前的Gap:lock_mode X);

c 未找到满足条件的记录,则对第一个不满足条件的记录加Gap锁,保证没有满足条件的记录插入(locks gap before rec)




 其次需要大家注意的是对比两个死锁案例会发现,sess1 事务持有的锁类型发生了变化 delete持有的锁变为lock_mode X locks rec but not gap 。 insert语句持有的锁变为 lock mode S waiting。原因是因为测试表结构发生了变化字段a由普通索引变为唯一键,RR模式下对唯一键操作是没有gap锁的,而且insert 写入含有唯一键的数据是会申请GAP锁的特殊情况 Insert Intention Lock.
本例我们依然根据事务执行的时间顺序来解释,这样比较好理解。
a 根据死锁日志显示 事务2 也即sess1执行的事务,根据 HOLDS THE LOCK(S)显示
sess1 先执行 delete from ty where a=5 ,该事务持有索引a=5 的行锁lock_mode X locks rec but not gap。因为本例中a是唯一键,故没有gap锁。
b 事务1的日志也即sess2执行的事务,申请对 a=5 加锁(X Next-key Lock),一个rec lock 但是因为sess1中delete 已经执行完成,记录无效没有被删除,锁还没释放,故sess2的事务1等待sess1的事务2释放a=5的锁资源,日志中提示 lock_mode X waiting. 
c 然后根据WAITING FOR THIS LOCK TO BE GRANTED,提示事务2 insert语句正在等待 lock mode S waiting,为什么这次是 S 锁呢?因为a字段是一个唯一索引,所以insert语句会在插入前进行一次duplicate key的检查,需要申请S锁防止其他事务对a字段进行重复插入。而插入意向锁与T1已经insert语句必须等待前面 sess2中delete 获取a=5的行锁并且释放锁。
于是,sess2(delete) 等待sess1(delete) ,sess1(insert)等待sess2(delete),循环等待,造成死锁

四 小结
   本文研究了RR事务隔离级别下,普通索引与唯一键两种情况的死锁场景。如何避免解决此类死锁?推荐使用RC隔离级别+ ROW BASE BINLOG . 但是对于RC/RR模式下 ,insert 遇到唯一键冲突的时候的死锁不可避免。需要开发在设计表结构的时候 减少unique 索引设计。推荐文章 《不同语句模式下的锁类型
如果您觉得能从本文收益,可以请北在南方一瓶饮料 ^_^

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

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

【MySQL】死锁案例之二

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

【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 死锁成因&&检测方法      左图那两

mysql去重案例,group_concat函数的用法

mysql 去重案例: select group_concat(distinct user_id), `tenant_id` ,  `create_time`  from `dtops_db_list` where `create_time`  >='2016-07-22 00:00:00' and `create_time` <='2016-07-28 23:59:59'  group by `tenant_id`  1.建表语句 wjj@>create table dtstack(

SQL Server并行死锁案例解析

原文:SQL Server并行死锁案例解析 并行执行作为提升查询响应时间,提高用户体验的一种有效手段被大家所熟知,感兴趣的朋友可以看我以前的博客SQL Server优化技巧之SQL Server中的"MapReduce", SQL Server优化器特性-位图过滤(Bitmap),然而正如我一直强调的,任何事物均有利弊,重点在于抉择.近日有朋友问我关于在今年7月份SQL Saturday中分享的并行执行中关于并行死锁的内容,这里我就详细解释下我举的实例中的并行死锁.      并行死锁

MySQL死锁问题分析及解决方法实例详解_Mysql

MySQL死锁问题是很多程序员在项目开发中常遇到的问题,现就MySQL死锁及解决方法详解如下: 1.MySQL常用存储引擎的锁机制 MyISAM和MEMORY采用表级锁(table-level locking) BDB采用页面锁(page-level locking)或表级锁,默认为页面锁 InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁 2.各种锁特点 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低 行级锁:开销大,加锁慢;

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

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