背景说明:
公司内部一套自建分布式交易服务平台,在POC稳定性压力测试的时候出现了数据库死锁。(InnoDB引擎)由于保密性,假设是app_test表死锁了。
现象:
发生异常:Deadlock found when trying to get lock; try restarting transaction
分析思路:
1、回忆和查找相关资料,InnoDB死锁导致的原因。
第一:涉及多表访问,两个事务相互占有对方需要的锁。假设有A表(含有初始化记录1)和B表(含有初始化记录2)。进行如下操作会发生死锁。(先设置set autocommit=0; 不自动提交事务)
事务1: updateA表记录1,where条件带主键索引。 事务2: updateB表记录2 ,where条件带主键索引
事务1:updateB表记录2 ,where条件带主键索引 事务2: updateA表记录1,where条件带主键索引 (Deadlock)
第二:单表单记录访问,两个事务相互占有对方需要的锁。假设有A表(含有初始化记录1) 进行如下操作会发生死锁 (先设置set autocommit=0; 不自动提交事务)
事务1: select A表记录1 lock in share mode where条件带主键索引。 事务2:select A表记录1 lock in share mode where条件带主键索引。
事务2: updateA表记录1 where条件带主键索引 事务2: updateA表记录1 where条件带主键索引 (Deadlock)
2、查看根据死锁的sql语句找到代码逻辑。下面代码逻辑就仅仅以操作数据库的形式体现。(毕竟数据库经过这么多人的验证,先怀疑是否程序操作数据库有问题。)
begin; select liusmc ,huancdx ,buchang ,liuszdz ,liuszxz ,dangqzh from app_test where liusbm = 'chkseq' AND xitongbs = '110' AND farendma = '985' for update; UPDATE app_test SET liusmc = 'chkseq', huancdx = 10000, buchang = 1, liuszdz = 99999999, liuszxz = 0, dangqzh = 3380100 WHERE liusbm = 'chkseq' AND xitongbs = '110' AND farendma = '985' ; commit;
3、根据代码逻辑分析,sql采用的是 for update 添加的排他锁,而且是单表操作,还检查了表的索引情况(此表无索引,此处很关键),于是潜意识里面 排除了,第一和第二个导致死锁的原因。此处又陷入死胡同。能想到的两个原因都排除了。
4、于是想使用show engine innodb status 命令来查询Deadlock的具体信息究竟是那两个事务锁住了什么资源,导致了死锁。但是奈何没有数据库权限。又不好意思直接问甲方的管理员要。所以还是先自己梳理思路,查询相关资料。
5、后来决定模拟下代码逻辑操作数据库的场景。毕竟就一个事务select for update了一个表然后再update,两个事务执行这个的先后顺序,以及查询的是否相同记录也是容易模拟。于是模拟了以下场景。
场景模拟:
1、两个事务,事务1 先select for update 记录1 ,事务2 select for update 记录1 ;事务1 update记录1 (此场景不会死锁)
2、两个事务,事务1 先select for update 记录1 ,事务2 select for update 记录2 ;事务1 update记录1 (居然死锁发生了,不是说InnoDB引擎 在表无索引的情况下是锁表的么?知识范围有缺陷还是深度不够?此时的我真的在心中万马奔腾啊)
思绪良久,翻阅了好多资料,实在想不通了,于是厚着脸皮也不怕影响POC成绩的情况下,请教了甲方老的DBA协助查看下。DBA叫我复现以下,我说明了现象以及原因。然后也复现了。
DBA的回复是:
事务1 持有锁 事务2需要事务1持有的锁,事务1 的update操作需要获取锁,所以出现了回路,发生死锁。外带了一句主要问题where条件多列检测索引。解决方法就是添加索引。由于DBA领导一句话,要是这个原因清楚了,此话题就到此结束。所以没敢多问,环境维护人员漏加索引了(此表本来有主键索引的)。
对于DBA的这个解释,”还是没有懂,事务1持有锁,事务2需要事务1持有的锁。事务1 update操作需要获取这个锁,所以出现了回路,发生了死锁。” 事务2只是在等待这个事务1 占有的锁而已,为何事务1的update操作需要获取锁会被事务2 占有。难道在等待也是算占有的? 如果等待也算是占有的话,那么场景1模拟的也应该会是死锁。所以明显不是。
DBA查看的日志信息的时候给了一个截图很关键,app_test 的索引 gen_clust_index 被锁住了。我觉得原因出在这个隐藏主键上面。InnoDB在表无唯一索引和主键的情况下,会自动创聚焦索引。
select * from information_schema.INNODB_LOCKS;
select * from information_schema.INNODB_LOCK_WAITS;
当两个事务进程 select for update where条件 使用同一个主键的时候,INNODB_LOCKS 会有两个事务进行的锁定信息,INNODB_LOCK_WAITS也会有一条记录,这个问题,以后待补充查证吧。