mysql意向锁死锁排查

最近遇到一个奇怪的mysql死锁:

事务1:

begin;
   select * from bridge_tx_status where id = 2 for update; 1⃣️
   update bridge_tx_status set biz_nonce="1234" where transaction_id="23"; 2⃣️
commit;

事务2:

begin;
   update bridge_tx_status set transaction_id="23" where id =2; 1⃣️
commit;

事务级别为可重复读,其中id=2的列,transaction_id这一列为"23",且transaction_id上没有任何索引。

复现:首先执行事务1:的第一条sql,然后执行事务2的第一条sql但是不提交,然后执行事务1的第二条sql,此时会抛出死锁。

排查过程:

首先发生死锁我们可以得出结论,事务2的“update bridge_tx_status set transaction_id="23" where id =2”语句有不止一次加锁过程。

然后把事务1中第二条语句改成与事务2中update一致:

事务1:

begin;
   select * from bridge_tx_status where id = 2 for update; 1⃣️
   update bridge_tx_status set transaction_id="23" where id =2; 2⃣️
commit;

重新执行复现过程,发现死锁消失。

这时我们可以得出结论,“update bridge_tx_status set transaction_id="23" where id =2”语句的多次加锁中,存在一把锁,该锁与自身是兼容的,即该锁可以在不同事务中多次进入。

进一步查阅了mysql相关锁的资料后发现了一个叫意向锁的锁恰好满足这个条件,相关资料:https://juejin.im/post/5b85124f5188253010326360

简单概括一下就是mysql在加行锁之前要先对全表加意向锁,同一张表对意向锁与意向锁是兼容的,但是意向锁与表锁是冲突的,该锁主要是为了优化行锁与表锁的冲突检测过程。

由于transaction_id没有任何索引,所以该语句需要加表锁, 此时基本上整个过程已经比较清晰了:

事务一(select * from bridge_tx_status where id = 2 for update):

1.对整张表加意向ix锁。

2.对id=2的行加 record行锁。

 

事务二(update bridge_tx_status set transaction_id="23" where id =2):

1.对整张表加意向ix锁。

2.对id=2的行加 record行锁。(被事务1阻塞)

 

事务一(update bridge_tx_status set biz_nonce="1234" where transaction_id="23"):

1.对整张表加表锁。(被事务2阻塞)

 

由于可以看错已经成环,所以形成死锁。

 

你可能感兴趣的:(mysql,java)