2022-10-27 368
Part1 背景
锁作为 MySQL 知识体系的主要部分之一,是每个 DBA 都需要学习和掌握的知识。锁保证了数据库在并发的场景下数据的一致性,同时锁冲突也是影响数据库性能的因素之一。而锁冲突中,有一类很经典的场景经常会拿出来讨论:死锁。最近刚好也遇到了一个典型的死锁案例,本文会基于这个案例,做一次详细的分析与拆解。
Part2 问题
由于innodb engine status会记录最近一次死锁的细节信息,因此案例现场的信息是可以完整拿到的。用户针对这个死锁的问题,提出了疑问:数据更新的并不是同一行,使用的也是不同的索引,为什么会发生死锁?(以下细节信息均已脱敏)
死锁的两个语句如下:
UPDATEtbl_deadlockSETcol1=1,col2=1,update_time=1603685523WHERE(id1=6247476)AND(id2=74354) UPDATEtbl_deadlockSETcol1=1,col2=1,update_time=1603685523WHERE(id1=6249219)AND(id2=74354)
精简之后的 MySQL 死锁信息如下:
===================================== 2020-10-2612:14:307fd2642f5700INNODBMONITOROUTPUT ===================================== ...省略... ------------------------ LATESTDETECTEDDEADLOCK ------------------------ 2020-10-2612:12:037fd2846ed700 ***(1)TRANSACTION: TRANSACTION1795660514,ACTIVE0secstartingindexread mysqltablesinuse3,locked3 LOCKWAIT4lockstruct(s),heapsize1184,3rowlock(s) MySQLthreadid21829887,OSthreadhandle0x7fd28d14a700,queryid178279444172.21.0.15usernameupdating UPDATEtbl_deadlockSETcol1=1,col2=1,update_time=1603685523WHERE(id1=6247476)AND(id2=74354) ***(1)WAITINGFORTHISLOCKTOBEGRANTED: RECORDLOCKSspaceid8575pageno286947nbits1048index`id2`oftable`deadlock`.`tbl_deadlock`trxid1795660514lock_modeXwaiting Recordlock,heapno429PHYSICALRECORD:n_fields2;compactformat;infobits0 0:len4;hex00012272;asc"r;; 1:len4;hex00721f45;ascrE;; ***(2)TRANSACTION: TRANSACTION1795660513,ACTIVE0secfetchingrows mysqltablesinuse3,locked3 20lockstruct(s),heapsize2936,40rowlock(s) MySQLthreadid21905203,OSthreadhandle0x7fd2846ed700,queryid178279443172.21.0.15usernameupdating UPDATEtbl_deadlockSETcol1=1,col2=1,update_time=1603685523WHERE(id1=6249219)AND(id2=74354) ***(2)HOLDSTHELOCK(S): RECORDLOCKSspaceid8575pageno286947nbits1048index`id2`oftable`deadlock`.`tbl_deadlock`trxid1795660513lock_modeX Recordlock,heapno429PHYSICALRECORD:n_fields2;compactformat;infobits0 0:len4;hex00012272;asc"r;; 1:len4;hex00721f45;ascrE;; Recordlock,heapno430PHYSICALRECORD:n_fields2;compactformat;infobits0 0:len4;hex00012272;asc"r;; 1:len4;hex00721fe3;ascr;; Recordlock,heapno431PHYSICALRECORD:n_fields2;compactformat;infobits0 0:len4;hex00012272;asc"r;; 1:len4;hex0072218f;ascr!;; ...省略很多Recordlock... ***(2)WAITINGFORTHISLOCKTOBEGRANTED: RECORDLOCKSspaceid8575pageno344554nbits120index`PRIMARY`oftable`deadlock`.`tbl_deadlock`trxid1795660513lock_modeXlocksrecbutnotgapwaiting Recordlock,heapno9PHYSICALRECORD:n_fields44;compactformat;infobits0 0:len4;hex00722663;ascr&c;; ...省略无关的两行... 3:len4;hex005f5434;asc_T4;; 4:len4;hex00012272;asc"r;; ...省略很多行... ***WEROLLBACKTRANSACTION(1) ...省略...
Part3 原因分析
首先简单了解一下死锁的几个要素:
1. 互斥条件:一个资源每次只能被一个进程占用。
2. 请求与保持条件:资源请求被阻塞时,已持有的资源不会被释放。
3. 不剥夺条件:已获得的资源,在末使用完之前,不能强行剥夺。
4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系,通常会表现为有向环。
由于 MySQL 的锁机制的原因,只需要判断出两个 SQL 语句的锁存在循环等待,那么死锁的条件就会成立了。
接下来对 MySQL 记录的死锁信息进行详细的分析,首先观察死锁的事务详情这一部分信息:
LOCKWAIT4lockstruct(s),heapsize1184,3rowlock(s)。...... 20lockstruct(s),heapsize2936,40rowlock(s)
可以很明显可以发现,这两个语句涉及到的数据行还是比较多的,用户的疑问:数据更新的并不是同一行,其实是个误解。那么理论上,“循环等待:互相持有对方需要的锁”,这种典型的死锁场景是可能会存在的。
接下来,重点放在更细节的信息上:
***(1)WAITINGFORTHISLOCKTOBEGRANTED: RECORDLOCKSspaceid8575pageno286947nbits1048index`id2`oftable`deadlock`.`tbl_deadlock`trxid1795660514lock_modeXwaiting Recordlock,heapno429PHYSICALRECORD:n_fields2;compactformat;infobits0 0:len4;hex00012272;asc"r;; 1:len4;hex00721f45;ascrE;; ...... ***(2)WAITINGFORTHISLOCKTOBEGRANTED: RECORDLOCKSspaceid8575pageno344554nbits120index`PRIMARY`oftable`deadlock`.`tbl_deadlock`trxid1795660513lock_modeXlocksrecbutnotgapwaiting Recordlock,heapno9PHYSICALRECORD:n_fields44;compactformat;infobits0 0:len4;hex00722663;ascr&c;; ...省略无关的两行... 3:len4;hex005f5434;asc_T4;; 4:len4;hex00012272;asc"r;; ...省略很多行...
用户提出的疑问:使用的也是不同的索引,为什么会发送死锁?实际上二级索引上的记录锁,最终也会加到主键上。
这个很好理解,如果二级索引上,通过搜索商品表的商品名称索引(二级索引)搜索“iphone12”,并给这一行数据加上了锁,锁住了“iphone12”这个商品的详情数据行,如果别的事务可以通过搜索主键来修改这一行数据,明显是不行的。
因此本案例中,虽然死锁信息中记录的索引名称不一样,但是锁争用的条件是成立的,即:trx1 通过二级索引向主键上执行了加锁操作,而 trx2 在其他的二级索引上拿到了锁,但是主键锁拿不到,因此进入了等待状态。所以只需要定位到具体锁的数据,找到循环等待的逻辑关系,就可以完成整个案例分析了。
参考上文引用的信息,具体发生死锁的行的信息都记录在类似0: len 4; hex 00722663; asc r&c;;的信息中。
trx1 记录的锁等待信息是二级索引 id2,因为 id2 是一个单行索引,因此只会有 0 和 1 两行信息,0 代表的就是具体的行 id2,1 即为主键。通过 16 进制转换工具,转成 10 进制,可以发现对应的数据如下:
pk = 7479109 and id2 = 74354
那么再看看 trx2 记录的信息,锁等待方面,记录的信息是主键,所以这个地方会有完整的表数据,过滤掉无效的数据之后,留下了三行:0 为主键,3 为 id1,4 为 id2。转换进制之后,对应的数据如下:
pk = 7480931 and id1 = 6247476 and id2 = 74354
可以看到,trx2 等待的锁,id1 和 id2 刚好满足 trx1 的查询条件。而 trx2 持有的锁信息中,第一个刚好就是 trx1 等待的:
trx2 持有的锁
那么关于这个死锁案例的具体场景,就可以用下有向环的图例进行说明:
死锁图例
至此为止,这个死锁的案例分析就完成了,从最初的死锁成立条件分析,到解读具体的锁内容,最终完成了死锁的有向环图例。
实际上,自己观察一下这个死锁的有向环图例,会发现这两个语句用到了两个单列索引,那么进一步思考的话,如果这两个列建成了联合索引,这个死锁的案例是不是就可能不会发生了?
Part4 总结
对于死锁的问题,只需要根据四个条件,一步一步过滤与分析,通过解读死锁现场的详细内容,就可以准确的还原整个死锁的发生原因以及涉及到的数据行。当然,在实际的业务环境中,可能还会有更复杂和隐蔽的死锁案例,但是不论多么隐蔽和复杂,死锁分析的思路和步骤都是相似的。
关于专栏
《腾讯云数据库专家服务》是由腾讯云数据库技术服务团队维护的社区专栏,涵盖了各类数据库的实际案例,最佳实践,版本特性等内容。
原文链接:https://77isp.com/post/10232.html
=========================================
https://77isp.com/ 为 “云服务器技术网” 唯一官方服务平台,请勿相信其他任何渠道。
数据库技术 2022-03-28
网站技术 2022-11-26
网站技术 2023-01-07
网站技术 2022-11-17
Windows相关 2022-02-23
网站技术 2023-01-14
Windows相关 2022-02-16
Windows相关 2022-02-16
Linux相关 2022-02-27
数据库技术 2022-02-20
抠敌 2023年10月23日
嚼餐 2023年10月23日
男忌 2023年10月22日
瓮仆 2023年10月22日
簿偌 2023年10月22日
扫码二维码
获取最新动态