【MySQL】死锁案例之一

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 一 前言   死锁,其实是一个很有意思,也很有挑战的技术问题,大概每个DBA和部分开发同学都会在工作过程中遇见过 。关于死锁我会持续写一个系列的案例分析,希望能够对想了解死锁的朋友有所帮助。
一 前言
   死锁,其实是一个很有意思,也很有挑战的技术问题,大概每个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. 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. 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 索引设计。推荐文章 《 不同语句模式下的锁类型
如果您觉得能从本文收益,可以请北在南方一瓶饮料 ^_^



相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
1天前
|
存储 关系型数据库 MySQL
MySQL bit类型增加索引后查询结果不正确案例浅析
【8月更文挑战第17天】在MySQL中,`BIT`类型字段在添加索引后可能出现查询结果异常。表现为查询结果与预期不符,如返回错误记录或遗漏部分数据。原因包括索引使用不当、数据存储及比较问题,以及索引创建时未充分考虑`BIT`特性。解决方法涉及正确运用索引、理解`BIT`的存储和比较机制,以及合理创建索引以覆盖各种查询条件。通过`EXPLAIN`分析执行计划可帮助诊断和优化查询。
|
4天前
|
存储 SQL 关系型数据库
深入MySQL锁机制:原理、死锁解决及Java防范技巧
深入MySQL锁机制:原理、死锁解决及Java防范技巧
|
4天前
|
SQL JavaScript 关系型数据库
Mysql索引不当引发死锁问题
本文通过真实案例解析了MySQL在高并发环境下出现死锁的问题。数据库表`t_award`包含多个索引,但在执行特定SQL语句时遭遇索引失效,导致更新操作变慢并引发死锁。分析发现,联合索引`(pool_id, identifier, status, is_redeemed)`因`identifier`允许为空值而导致索引部分失效。此外,`pool_id`上的普通索引产生的间隙锁在高并发下加剧了死锁风险。为解决此问题,文中提出了调整索引顺序至`(pool_id, status, is_redeemed, identifier)`等方案来优化索引使用,进而减轻死锁现象。
|
8天前
|
Oracle 关系型数据库 MySQL
Mysql和Oracle数据库死锁查看以及解决
【8月更文挑战第11天】本文介绍了解决MySQL与Oracle数据库死锁的方法。MySQL可通过`SHOW ENGINE INNODB STATUS`查看死锁详情,并自动回滚一个事务解除死锁;也可手动KILL事务。Oracle则通过查询V$LOCK与V$SESSION视图定位死锁,并用`ALTER SYSTEM KILL SESSION`命令终止相关会话。预防措施包括遵循ACID原则、优化索引及拆分大型事务。
|
22天前
|
SQL 存储 关系型数据库
细说 MySQL 死锁
【7月更文挑战第26天】MySQL 死锁
26 4
|
20天前
|
SQL 算法 关系型数据库
(十)全解MySQL之死锁问题分析、事务隔离与锁机制的底层原理剖析
经过《MySQL锁机制》、《MySQL-MVCC机制》两篇后,咱们已经大致了解MySQL中处理并发事务的手段,不过对于锁机制、MVCC机制都并未与之前说到的《MySQL事务机制》产生关联关系,同时对于MySQL锁机制的实现原理也未曾剖析,因此本篇作为事务、锁、MVCC这三者的汇总篇,会在本章中补全之前空缺的一些细节,同时也会将锁、MVCC机制与事务机制之间的关系彻底理清楚。
|
1月前
|
SQL 存储 关系型数据库
细说 MySQL 死锁
死锁检查在MySQL 8.0中涉及三个主要步骤:构造锁等待图、初始化事务权重和提升权重。首先,当事务进入锁等待状态时,信息会被记录到内存中的`waiting_threads`,形成快照数组。接着,对这个数组进行排序,构造出锁等待图,表示事务间的等待关系。然后,初始化所有等待事务的权重为1,如果一个事务在其他事务等待后进入等待,其权重会被提升,以避免长时间等待。最后,根据锁等待图,提升那些同时阻塞其他事务的权重,但不包括参与死锁的事务。权重更新后,死锁检查线程将依据这些信息来检测和解决死锁。
57 15
|
27天前
|
缓存 监控 关系型数据库
MySQL PXC 集群死锁分析案例
前不久一个系统死锁导致部分业务受到影响,今次补上详细的节点日志分析过程。
38 1
|
2月前
|
SQL 监控 关系型数据库
深入解析MySQL死锁:原因、检测与解决方案
深入解析MySQL死锁:原因、检测与解决方案
|
2月前
|
SQL Java 数据库连接
2万字实操案例之在Springboot框架下基于注解用Mybatis开发实现基础操作MySQL之预编译SQL主键返回增删改查
2万字实操案例之在Springboot框架下基于注解用Mybatis开发实现基础操作MySQL之预编译SQL主键返回增删改查
47 2