MySQL死锁的案例

文章目录

  • 死锁的发生
  • 问题分析
  • 解决方案
  • 总结

死锁的发生

今天碰到一个MySQL死锁的案例,应用启动时发生了死锁,先看日志吧:

2019-03-18 19:40:24 jdbc.sqltiming [ERROR] 45. PreparedStatement.execute() FAILED!    DELETE FROM NGB_DNSMAP_CHAIN_INFO   WHERE dnsSystemId = '17' and dnsmapId = '1563290923705244' and bakDns' and dnsmapSwitchStrategy = '1' and dnsmapRecoverStrategy = '1' and isClusterDispatch = '1' and isPopDispatch = '0' and isAlarmSwitch = '1'   {FAILED after 1364 msec}
com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
	at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
	at com.mysql.jdbc.Util.getInstance(Util.java:386)
	at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1066)
	at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4190)
	at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4122)

具体SQL语句如下:

DELETE FROM NGB_DNSMAP_CHAIN_INFO   WHERE dnsSystemId = '17' and dnsmapId = '1563290923705244' 
and bakDns' and dnsmapSwitchStrategy = '1' and dnsmapRecoverStrategy = '1' 
and isClusterDispatch = '1' and isPopDispatch = '0' and isAlarmSwitch = '1'

表结构如下:
MySQL死锁的案例_第1张图片

  • id:主键
  • dnsSystemId:索引
  • dnsmapId:索引

我们使用的是MySQL5.6版本,InnoDB引擎,默认的事务隔离级别(REPEATABLE-READ)

问题分析

首先这个DELETE是批量操作,但并非多线程操作,而是在同一个线程进行操作的,不存在并发问题,因此可以排除同一语句产生死锁的情况。

那么,势必存在另一个或多个语句在和该DELETE语句竞争锁资源,于是我们找到了一个shell脚本会定期执行如下语句(后续沟通发现该脚本是某次迭代更新使用的,没有进行闭环操作),而且有且只有这么一条语句:

UPDATE ngb_dnsmap_chain_info SET isClusterDispatch=1

在继续分析之前,先了解下MySQL InnoDB引擎的锁机制吧。

  • 表锁

    • 如果查询条件中没有使用到索引字段,则都会获取表锁
    • 即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁
  • 行级锁

    • 如果查询条件中使用到索引字段,则会获取行级锁。
    • 行级锁是针对索引加的锁,不是针对数据记录加的锁,所以虽然是访问不同行的记录,但是如果使用相同的索引键,是会出现锁冲突的。
    • 当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁

继续分析,姑且给两个语句定义一个名称吧

A语句:

DELETE FROM NGB_DNSMAP_CHAIN_INFO   WHERE dnsSystemId = '17' and dnsmapId = '1563290923705244' 
and bakDns' and dnsmapSwitchStrategy = '1' and dnsmapRecoverStrategy = '1' 
and isClusterDispatch = '1' and isPopDispatch = '0' and isAlarmSwitch = '1'

B语句:

UPDATE ngb_dnsmap_chain_info SET isClusterDispatch=1

A语句要执行,会使用到索引锁,先根据索引锁锁住行数据,再去锁住对应的主键

B语句是全表锁,会修改主键和索引记录,因此会先获取主键锁,再获取索引锁

B获取主键锁等待索引锁释放,A获取索引锁等待主键锁释放,因此发生死锁

解决方案

后续将shell脚本停止,成功启动应用

总结

MySQL内部InnoDB引擎的锁机制远远比上面提到的要复杂,在应用中,避免死锁最好的手段就是按顺序获取锁或者根据主键去修改数据。

关于MySQL锁的说明网上资料非常多,这里列出我参考的一篇文章:
https://www.cnblogs.com/aipiaoborensheng/p/5767459.html

你可能感兴趣的:(MySQL)