业务新上了一个功能,在发布的过程当中,系统报出了数据库死锁异常:mysql
com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
死锁发生在一个事务中,事务对多个表进行了操做。在报错日志中,死锁发生在tableA与tableB。一开始怀疑这次发布的某个改动中对上面这两张表新增了select或update操做。将注意力用在排查这个问题上。排查后发现没有相关的变动,又猜想是不是因为更改形成并发请求进来,接口原来是有加分布式锁的,需求更改中缩小了分布式锁的粒度,确实是有可能形成并发请求。但很快又否认了,秒杀场景下的并发量尚且不会发生死锁,况且是这个接口?以为问题应该别有所在。先回滚了需求后,联系dba执行了命令SHOW ENGINE INNODB STATUS
将死锁日志拉取了出来:sql
从死锁日志能够看到事务(1)TRANSACTION
尝试更新表A,等待表A的锁(1)WATING FOR THIS LOCK TO BE GRANTED
.但此时另一个事务(2)TRANSACTION
已经持有了表A的锁:(2)HOLDS THE LOCK(S)
,同时也在等待表B的锁(2)WATING FOR THIS LOCK TO BE GRANTED
. 状况可能以下所示:数据库
事务(1) | 事务(2) |
---|---|
持有表1的写锁,并更新了表1 | |
等待表1的写锁 | |
等待表2的写锁 |
因为事务2一直都获取不到表2的写锁,事务2没法提交,所以事务2持有的表1锁没有释放(在事务执行过程当中,若是有加锁操做,这个锁须要等事务提交时释放),致使事务1一直在等待表1的写锁,从而最终致使死锁。那么表2的写锁被哪一个事务持有了?有没有多是事务1?也便是下面这种状况:并发
事务(1) | 事务(2) |
---|---|
持有表2的写锁,并更新了表2 | |
持有表1的写锁,并更新了表1 | |
等待表1的写锁 | |
等待表2的写锁 |
因为更新的是同一个用户的同一行记录,这种状况可能在sql执行顺序不一致所致使,因此对比了需求变动先后的事务逻辑,果真发现了端倪:分布式
变动前:rest
事务开始 「 更新表1 更新表2 」 事务提交
变动后:日志
事务开始 「 更新表2 更新表1 」 事务提交
在发布的过程当中,有部分机器的代码处于变动前,有部分机器的代码处于变动后,最终致使了上述的死锁问题。此时缘由已经明了,可是疑惑的是为何死锁出现时立刻就报错,不会进行等待?查阅文档发现:死锁检测开了后,发生死锁会立马回滚。死锁检测关闭,锁等待超时时间这个设置才会生效:code