往期分享
RDS MySQL
RDS PostgreSQL
RDS SQL Server
PolarDB
Redis
MongoDB
背景
死锁是关系型数据库系统中最为常见的错误,出现在不同事务中同时对某些数据访问加锁时都要等待对方请求中的数据而无法获取锁,数据库系统会自动牺牲回滚代价最小的事务,从而导致对应的写请求失败,更严重的情况是在大量死锁发生时,会导致数据库系统效率低下,堆积进程大量堆积引发性能问题。
一般来说,死锁都是由于逻辑加锁的顺序导致的,也就是我们常说的 ABA死锁,举例:
tran_A 与 tran_B两个请求分别持有对方所需要的第二次update的行锁,就形成了死锁:
此时业务会收到报错信息类似:
Error : Deadlock found when trying to get lock; try restarting transaction
观察数据库内信息:
show engine innodb status\G; ------------------------ LATEST DETECTED DEADLOCK ------------------------ 2020-12-01 16:43:28 0x7fe8a0277700 *** (1) TRANSACTION: TRANSACTION 370942954, ACTIVE 9 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1 MySQL thread id 25713, OS thread handle 140637097146112, query id 237499 106.11.34.226 luhuo_h updating update sbtest1 set c='tran2_tran1' where id=1 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 354 page no 4 n bits 144 index PRIMARY of table `sbtest`.`sbtest1` trx id 370942954 lock_mode X locks rec but not gap waiting Record lock, heap no 2 PHYSICAL RECORD: n_fields 6; compact format; info bits 0 0: len 4; hex 80000001; asc ;; 1: len 6; hex 0000161c23e9; asc # ;; 2: len 7; hex 23000000151374; asc # t;; 3: len 4; hex 80c52f66; asc /f;; 4: len 30; hex 7472616e3120202020202020202020202020202020202020202020202020; asc tran1 ; (total 120 bytes); 5: len 30; hex 32323139353230373034382d37303131363035323132332d373431343033; asc 22195207048-70116052123-741403; (total 60 bytes); *** (2) TRANSACTION: TRANSACTION 370942953, ACTIVE 15 sec starting index read mysql tables in use 1, locked 1 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1 MySQL thread id 25568, OS thread handle 140637096081152, query id 237597 106.11.34.226 luhuo_h updating update sbtest1 set c='tran1_tran2' where id=2 *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 354 page no 4 n bits 144 index PRIMARY of table `sbtest`.`sbtest1` trx id 370942953 lock_mode X locks rec but not gap Record lock, heap no 2 PHYSICAL RECORD: n_fields 6; compact format; info bits 0 0: len 4; hex 80000001; asc ;; 1: len 6; hex 0000161c23e9; asc # ;; 2: len 7; hex 23000000151374; asc # t;; 3: len 4; hex 80c52f66; asc /f;; 4: len 30; hex 7472616e3120202020202020202020202020202020202020202020202020; asc tran1 ; (total 120 bytes); 5: len 30; hex 32323139353230373034382d37303131363035323132332d373431343033; asc 22195207048-70116052123-741403; (total 60 bytes); *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 354 page no 4 n bits 144 index PRIMARY of table `sbtest`.`sbtest1` trx id 370942953 lock_mode X locks rec but not gap waiting Record lock, heap no 3 PHYSICAL RECORD: n_fields 6; compact format; info bits 0 0: len 4; hex 80000002; asc ;; 1: len 6; hex 0000161c23ea; asc # ;; 2: len 7; hex 24000000191844; asc $ D;; 3: len 4; hex 8021f170; asc ! p;; 4: len 30; hex 7472616e3220202020202020202020202020202020202020202020202020; asc tran2 ; (total 120 bytes); 5: len 30; hex 32383733333830323932332d31303534383839343634312d313138363735; asc 28733802923-10548894641-118675; (total 60 bytes); *** WE ROLL BACK TRANSACTION (2)
但是引擎层打印出的太过于晦涩,难以定位问题,本文主要描述以云上已有工具进行业务逻辑的定位方法
另外,本文说的死锁是指deadlock,而非事务锁造成的阻塞(block),要区分排查。
定位
基础分析
通过实例控制台->一键诊断->锁分析入口进入,选择立即诊断,如果实例存在死锁,会在【发现死锁】列出现 【是】。
需要说明的是,目前诊断功能只能拉取最后一次死锁,同样是从innodb status中获取的,如果实例不重启,死锁信息会一直保留最后一组日志,所以需要确认诊断后的日志是不是存量死锁问题,也就是说发现死锁不一定是新出现的死锁。
目前DMS平台正在排期全量死锁记录的功能,到时会有真实的全量死锁信息。
发现死锁后,点击查询详情页,会显示格式化后的死锁信息:
- Thread id : 线程ID,和洞察中的线程ID对应
- 涉及表:死锁出现的表,有时可能左右表不一致,是因为事务中请求的表不致的问题
- 等待锁索引名: DML语句会将锁加在索引行上,所以获取不到的锁一定是在某个索引上
- 事务SQL : 引发死锁的语句
以上信息是一个简单死锁的基本情况,但是由于MySQL的死锁信息相对简单,如果是一组事务中的几个语句导致加锁顺序不对,在死锁信息中无法定位,如果是简单业务,可以将【事务SQL】给到研发人员进行语句级别的定位,但是由于有些业务逻辑过于复杂,开发也无法确认事务流,此时就需要进一步将整个事务进行定位。
事务流定位
事务流定位的前提条件是在死锁发生前,开启了sql洞察功能,才能对执行过的语句进行定位。
首先可以获取的信息是:
- 回滚的事务
- 发生死锁的语句
- thread_id
- 错误线程定位
牺牲事务thread_id 为 1622,成功thread id 为1746,先对牺牲事务进行定位:
状态中显示 【失败(1213)】,error 1213就是死锁回滚的code,所以可以定位发生回滚的事务:
默认返回是秒级排序,如果要获取时序的事务流,需要通过 【执行时间(毫秒)】进行排序,注意如果返回语句太多,将无法进行【执行时间(毫秒)】排序,需要继续缩小时间短,减小返回审计数据。
同时知道执行成功的thread id 为1746 ,可以再次进行定位:
分析日志可获取事务时间线:
至此死锁链的事务流已经分析出来,可以交由业务人员进行代码定位了。
注意事项
- 在查找SQL审计内容时,有可能出现大量的语句导致无法分析,需要不断的缩短时间范围以定位准确区间
- 需要明确出现的报错语句为error 1213错误才是死锁退出的语句
- 如果业务无没有开启事务,有可能是在框架中配置的,一般开始语句都是set autocommit=0,有begin开始事务的情况不是很多。