MySQL死锁产生机制、SELECT...FOR UPDATE死锁及解决办法咨询
MySQL死锁相关问题解答
1. MySQL死锁的产生及跨表死锁成因
死锁的本质是多个事务在持有锁的同时,互相等待对方释放锁,形成循环等待链,满足死锁的四个必要条件:互斥访问、持有并等待、不可剥夺、循环等待。
跨表死锁的逻辑和单表死锁一致,只是锁涉及多张无关表。举个典型场景:
- 事务1:
BEGIN; UPDATE table_a SET val=1 WHERE id=1;(持有table_a行1的排他锁) - 事务2:
BEGIN; UPDATE table_b SET val=2 WHERE id=2;(持有table_b行2的排他锁) - 事务1继续执行:
UPDATE table_b SET val=3 WHERE id=2;(等待事务2释放table_b行2的锁) - 事务2继续执行:
UPDATE table_a SET val=4 WHERE id=1;(等待事务1释放table_a行1的锁)
此时两个事务形成循环等待,触发死锁。两张表无关,但事务交叉请求锁的顺序相反,就会导致跨表死锁。
2. SELECT...FOR UPDATE的死锁问题
是否因执行顺序产生死锁?属实
是否初始就加排他锁?是
InnoDB引擎下,SELECT...FOR UPDATE会对查询命中的行立即加排他锁(X锁),锁的持有时间从语句执行开始,到事务提交或回滚结束。
死锁成因同样是循环等待:比如两个事务都用SELECT...FOR UPDATE锁行,但锁的顺序相反:
- 事务1:
BEGIN; SELECT * FROM table_x WHERE id=1 FOR UPDATE;(持有行1的X锁) - 事务2:
BEGIN; SELECT * FROM table_x WHERE id=2 FOR UPDATE;(持有行2的X锁) - 事务1执行:
SELECT * FROM table_x WHERE id=2 FOR UPDATE;(等待事务2释放行2的锁) - 事务2执行:
SELECT * FROM table_x WHERE id=1 FOR UPDATE;(等待事务1释放行1的锁)
这种情况下就会触发死锁,核心原因还是锁请求顺序形成了循环等待链。
3. 死锁的可行解决办法
- 统一锁顺序:所有事务严格按照相同的顺序访问表或行。比如操作多张表时,固定先锁table_a再锁table_b;锁行时按id从小到大的顺序请求锁,从根源避免循环等待。
- 缩短事务时长:尽量将事务拆分为小粒度操作,减少锁的持有时间,降低锁冲突概率。避免在事务中执行耗时的非数据库操作(如IO、外部API调用)。
- 调整隔离级别:使用
READ COMMITTED(读提交)隔离级别,InnoDB在此级别下会释放不匹配的行锁,锁的范围更小,能减少锁冲突。 - 避免无意义等待:使用
SELECT...FOR UPDATE SKIP LOCKED跳过已被锁定的行,或SELECT...FOR UPDATE NOWAIT在遇到锁时直接返回错误,避免进入等待队列。 - 死锁重试机制:InnoDB默认开启死锁检测,发生死锁时会自动回滚代价较小的事务。应用层可以捕获死锁错误(错误码1213),并重执事务。
- 合理选择锁类型:不需要排他锁时,改用
SELECT...FOR SHARE加共享锁,减少锁冲突的可能性。
内容的提问来源于stack exchange,提问作者Yang Xu
相关产品推荐
相关产品推荐

