MySQL里头并发操作一多,死锁这事儿就容易冒出来,真是让人头疼
- 问答
- 2026-01-25 16:30:27
- 56
MySQL里头并发操作一多,死锁这事儿就容易冒出来,真是让人头疼,这感觉就像一条窄路上,两辆车都想通过,但各自堵着对方的路,谁也不让谁,结果就卡死在那儿了,数据库里的死锁也差不多,两个或者更多的事务,每个都攥着自己已经拿到手的“锁”(比如锁住某一行数据),同时又在眼巴巴地等着对方手里的那个锁,结果大家就僵住了,谁也进行不下去。
根据MySQL官方文档和一些常见的经验,死锁在并发高的系统里几乎没法完全避免,它本身就是数据库管理系统中一个经典的问题,最容易出事儿的地方,往往就在我们日常写的那些业务逻辑里,比如说最常见的“转账”场景:事务A要更新账户1然后更新账户2,事务B正好反过来,先更新账户2然后更新账户1,如果这两个事务同时跑,很可能事务A锁住了账户1,事务B锁住了账户2,接着事务A想去锁账户2,发现被B占着,事务B想去锁账户1,发现被A占着,得,死锁就发生了,数据库引擎这时候会反应过来,它会选其中一个事务(通常是回滚代价更小的那个)立刻回滚,让另一个事务能继续完成,被选中的那个事务就会报出一个死锁错误。
还有一种很隐蔽的情况,跟索引有关系,比如你更新一条数据,条件是“根据某个非索引的字段”去找,那MySQL可能就得锁住很多行,甚至锁住整个表的一大部分,才能找到目标,这时候如果另一个事务也在操作这个表里的某些行,哪怕它们逻辑上不是同一条数据,也很容易因为锁的范围太大而撞车,很多人都会强调,要有合适的索引,让查询尽量精准地锁定目标,减少“误伤”。

订单和库存操作也是死锁重灾区,比如秒杀时,一堆人同时抢最后一个商品,事务都是先查库存(假设库存>0),然后去扣减库存,如果大家同时查到库存是1,都以为自己能买,接着去执行更新,MySQL会对这条库存记录加锁,但顺序上可能产生竞争,更复杂的是,有些设计为了记录明细,会在扣减库存后,紧接着在另一张“库存流水”表里插入一条记录,这个顺序如果设计得不一致,有的先更库存再插流水,有的先插流水再更库存,死锁的风险就会大大增加。
那怎么办呢?死锁不一定是“ bug”,在高并发下它有时是种常态,根据一些资深开发者的分享,首要的应对策略是让应用程序“优雅地重试”,就是捕获到死锁错误后,别慌,等一小会儿(比如随机休眠几十毫秒),然后自动重新执行刚才那个事务,大部分情况下,重试一两次就能成功,这要求你的业务逻辑在重试时是安全的,也就是操作要设计成“幂等”的,重复执行不会出错。

要尽量让事务短小精悍,一个事务里别做太多事,越快提交越好,占着锁的时间越短,跟别人撞上的几率就越低,该查的数据最好在事务开始前就查好,别在事务里做一堆无关的查询,还有,访问多张表或者多行数据时,如果可能,尽量约定一个固定的顺序,比如前面说的转账,如果所有业务都约定先更新ID小的账户,再更新ID大的账户,那么即使并发,大家也是排队等同一个方向上的锁,就不会形成循环等待。
得学会看死锁日志,MySQL在发生死锁时,可以通过命令(SHOW ENGINE INNODB STATUS)看到最近一次死锁的详细信息,日志里会画图一样告诉你,事务1在等着哪把锁,同时它自己拿着哪把锁;事务2又在等着哪把锁,拿着哪把锁,看懂这个,你就能精准定位到是哪些SQL语句,在争抢哪些数据,从而去优化你的代码或者索引。
调整数据库的隔离级别也有帮助,默认的“可重复读”级别为了防止幻读,会使用间隙锁,这可能会增加死锁的概率,对于一些可以接受“不可重复读”的业务场景,改用“读已提交”级别,能减少一部分锁冲突,但这也可能带来其他数据一致性问题,需要仔细权衡。
面对MySQL死锁,头疼归头疼,但也不是无计可施,核心思路就是:减少冲突的机会(短事务、好索引、固定顺序),以及准备好冲突后的退路(自动重试),把它当成一个正常的系统现象来设计和应对,而不是一个必须彻底消灭的敌人,心态会平和很多,随着对业务和数据库交互模式越来越熟悉,你预判和规避死锁的能力也会越来越强。
本文由称怜于2026-01-25发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://gctr.haoid.cn/wenda/85825.html
