背景
业务端遇到报错为"Deadlock found when trying to get lock; try restarting transaction"则表明有死锁发生
名称 | 配置 |
数据库版本 | GreatSQL 8.0.26 |
隔离级别 | Read-Commited |
innodb status 日志
查看表结构
梳理 innodb status 日志
- 整理如下:
事务 | T1 | T2 |
操作 | insert into info values (50,11) | insert into info values (60,8) |
关联的对象 | 表apple.info的唯一索引 uk_name | 表apple.info的唯一索引 uk_name |
持有的锁 | lock mode S waitingheap no 7 11,40(十六进制为8,28) | lock_mode X locks rec but not gapheap no 7 11,40(十六进制为8,28) |
等待的锁 | lock mode S waitingheap no 7 11,40(十六进制为8,28) | lock_mode X locks gap before rec insert intention waitingheap no 7 11,40(十六进制为8,28) |
- 首先事务T2获取到了uk_name中记录11的 lock x,rec not not gap 锁
- 事务T1尝试获取uk_name中记录11的lock s, next key lock,由于T2持有了记录的独占锁,因此被T1堵塞
- 事务T2尝试获取uk_name中记录11的lock x, gap before rec,insert intention,但被堵塞
获取业务历史SQL语句
通过系统表方式
通过performance_schema.threads、performance_schema.events_statements_history、performance_schema.events_statements_history_long等系统表获取历史SQL
- 根据GreatSQL thread id获得线程id
- 根据线程id获得线程历史SQL
- 观察show engine innodb status中的GreatSQL thread id 16和GreatSQL thread id 15
- 通过performance_schema.threads获取THREAD_ID
- 通过performance_schema.events_statements_history获取THREAD_ID执行的历史SQL以及执行时间
最终可复现出如下业务SQL:
事务 | T1 | T2 |
语句 | begin; | begin; |
语句 | insert into info values (40,11); | |
语句 | insert into info values (50,11); | |
语句 | insert into info values (60,8); |
通过解析binlog
$ mysqlbinlog -vv --base64-output=decode-rows bin.000030
根据binlog中部分SET @@SESSION.GTID_NEXT= 'e319a624-b2ce-11ee-9aac-00163e62ca8a:8697'该GTID的事务信息,可恢复T2,但T1执行的语句由于被回滚了,则不会记录到binlog,可开启general log日志获取排查
事务 | T1 | T2 |
语句 | begin; | begin; |
语句 | insert into info values (40,11); | |
语句 | insert into info values (50,11); | |
语句 | insert into info values (60,8); |
分析死锁
- T1、T2开启了一个事务
- 随后T2执行了插入(40,11)的insert语句:insert into info values (40,11)
- T1执行了插入(50,11)的insert语句:insert into info values (50,11) 进行唯一性冲突检查,尝试获取LOCK_S
- 然后T1所在的连接会将T2中的隐式锁转换为显示锁,此时T2将获取Lock X, Rec_not_gap。由于T2的Lock X, Rec_not_gap与T1的LOCK S不兼容,因此T1被堵塞
- 随后,T2又执行了(60,8)的insert语句:insert into info values (60,8) 由于其插入的唯一索引值是8,因此不存在主键冲突,直接执行乐观插入操作。执行乐观插入时,需要检查其它事务是否堵塞insert操作。其核心是获取待插入记录的下一个值(这里刚好是10),并获取该记录上的所有锁,与需要添加的锁判断是否存在冲突。
- T1持有了记录11的LOCK_S锁与T2的LOCK_X、LOCK_INSERT_INTENTION不兼容,因此T2被T1堵塞
- 死锁形成。
解决
• 适当的减少Unique索引
• 避免插入重复的值(唯一索引所在列)