update语句会发⽣死锁?
90% 的程序员都认为 innodb 是⾏级锁,但实际上使⽤不当,它也是表级锁!
看过我博客的⽹友都知道,我之前写过⼀篇⽂章《 InnoDB 的 select ⾏锁还是表锁?》。拯救过不少⼈,今天我们再来⼀次⼤拯救!
最近⽣产上的 MySQL 数据库,是不是的就来⼀次 DeadLock,其中我做了故障排查,昨天做了相关的升级,导致昨天⾮常的忙,很多⽹友加我好友,都没有及时回应,直到晚上升级结束,我在⾥做了相关的解释!
截了⼀段错误⽇志信息如下:
其中涉及到的更新语句如下:
很奇妙吧,执⾏⼀条 update sql 竟然会有死锁?
具体死锁的提前是 i_pay_record 表中的 order_id 字段有索引。
下⾯我们通过新建⼀张表 xttblog,来说明问题。
当我们执⾏下⾯的 update 语句时,就有可能发⽣死锁!批量更新sql语句
mysql 的事务⽀持与存储引擎有关,MyISAM 不⽀持事务,INNODB ⽀持事务,更新时可能采⽤的是⾏级锁,也可能是表级锁。我们这⾥采⽤的是 INNODB 做存储引擎,意味着会将 update 语句做为⼀个事务来处理。前⾯的⽂章中我提到了⾏级锁必须建⽴在索引的基础上,上⾯的更新语句⽤到了索引 idx_1,所以这⾥肯定会加上⾏级锁。
⾏级锁并不是直接锁记录,⽽是锁索引(前⾯的⽂章也解释过)。
如果⼀条 SQL 语句⽤到了主键索引,mysql 会锁住主键索引;如果⼀条语句操作了⾮主键索引,mysql 会先锁住⾮主键索引,再锁定主键索引。
这个 update 语句会执⾏以下步骤:
由于⽤到了⾮主键索引,⾸先需要获取 idx_1 上的⾏级锁
紧接着根据主键进⾏更新,所以需要获取主键上的⾏级锁
更新完毕后,提交,并释放所有锁
如果在步骤 1 和 2 之间突然插⼊⼀条语句:update xttblog ……where id=? and user_id=? 这条语句,那么会先锁住主键索引,然后锁住 idx_1。
这时,悲剧就发⽣了!
⼀条语句获取了 idx_1 上的锁,等待主键索引上的锁;另⼀条语句获取了主键上的锁,等待 idx_1 上的锁,这样就出现了死锁。
很惊奇吧,其实⼀点也不奇怪,只要你了解了 MySQL 的⼀些底层设计原理!
那么发⽣这种问题,有解决⽅案吗?
当然有了,要不然我写这篇⽂章⼲什么?
解决⽅案,最笨最靠谱的做法就是:先获取需要更新的记录的主键,然后再逐条更新!
这样就可以解决问题了,但是这个解决⽅案与先前的更新语句不⼀样,先前的更新语句对所有记录的更新在⼀个事务中,采⽤循环更新后并不在同⼀个事务中,所以在 for 循环外⾯还得开⼀个事务。
在采⽤ INNODB 的 MySQL 中,更新操作默认会加⾏级锁,⾏级锁是基于索引的,在分析死锁之前需
要查询⼀下 mysql 的执⾏计划,看看是否⽤到了索引,⽤到了哪个索引,对于没有⽤索引的操作会采⽤表级锁。如果操作⽤到了主键索引会先在主键索引上加锁,然后在其他索引上加锁,否则加锁顺序相反。在并发度⾼的应⽤中,批量更新⼀定要带上记录的主键,优先获取主键上的锁,这样可以减少死锁的发⽣。
不是说 update 不会发⽣死锁,⽽是你的程序没遇到⾼并发⽽已!关于死锁的故障分析排查,我们以后继续!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论