数据库死锁是什么——怎么排查和预防

王尘宇 问题解答 1

「数据库死锁」这个词听起来很技术,但本质上就是一个很简单的场景:两个事务互相等对方释放资源,结果谁都走不动。

举个例子。事务A锁住了表1的第3行,要去更新表2的第5行。同时事务B锁住了表2的第5行,要去更新表1的第3行。A等B释放表2的锁,B等A释放表1的锁,两边永远等不到对方——这就是死锁。

MySQL怎么判断死锁?MySQL有一个死锁检测机制(innodb_deadlock_detect,默认开启),发现死锁后会自动回滚其中一个事务,让另一个继续执行。被回滚的事务会收到错误:Deadlock found when trying to get lock。所以死锁不会让数据库卡住,但会导致事务失败,如果应用没有处理好重试逻辑,用户就会看到报错。

怎么排查死锁?MySQL提供了两个命令。第一个是SHOW ENGINE INNODB STATUS,里面有一段LATEST DETECTED DEADLOCK,记录了最近一次死锁的详细信息——哪些事务、锁了哪些行、执行了什么SQL。第二个是开启innodb_print_all_deadlocks参数,把所有死锁信息都写到错误日志里,方便事后分析。

排查的时候重点看三个信息:每个事务执行的SQL语句、每个事务持有的锁和等待的锁、事务的执行顺序。知道了这三个,基本就能定位问题了。

怎么预防死锁?五个实用方法。

第一个,统一加锁顺序。如果所有事务都按「先锁表1再锁表2」的顺序操作,就不会出现互相等待的情况。这是最根本的解决办法,但需要在代码层面统一规范。

第二个,缩短事务时间。事务持有的锁越多、时间越长,死锁概率越大。不要在事务里做网络请求、文件操作这些耗时的事情。拿到数据、计算完、立刻提交。

第三个,用索引减少锁范围。没有索引的UPDATE语句会锁全表,有索引只锁匹配的行。确保WHERE条件里的字段有索引,锁的范围会小很多,死锁概率也低。

第四个,降低隔离级别。MySQL默认的隔离级别是REPEATABLE READ,在这个级别下gap lock(间隙锁)会导致更多的锁冲突。如果你的业务允许,可以考虑降到READ COMMITTED,gap lock会少很多。但要评估脏读对业务的影响。

第五个,加锁失败自动重试。死锁无法100%避免,应用层要做好重试逻辑。检测到Deadlock错误后,等100ms重试,最多重试3次。大部分情况下重试就能成功。

一个实际案例:我之前遇到一个电商系统的死锁问题,下单和库存扣减两个操作的加锁顺序不一致,高峰期每分钟能触发两三次死锁。把加锁顺序统一后,问题直接消失了。所以排查死锁,第一步永远是看加锁顺序。

标签: 数据库 死锁 mysql 故障排查

发布评论 0条评论)

  • Refresh code

还木有评论哦,快来抢沙发吧~