mysql死锁排查解决
今天数据库突然报错[40001][1213] Deadlock found when trying to get lock; try restarting transaction 一看就是死锁
阿里实列会显示类似sql:UPDATE goods
SET `num` = `num` - 1
WHERE id=2 AND `num` >= 1;
一看sql这不是扣减库存操作吗? 为什么这sql会出现死锁????????让我百思不得其姐 不得其姐啊
经过复现排查代码 原来 我们扣减库存是 商品ID 从redis购物车读出来 没有统一排序 类似
和
如图上图所示
死锁原因解析
-
并发事务交叉加锁
-
事务1 按顺序更新
id=1
→id=2
,先锁定id=1
。 -
事务2 按顺序更新
id=2
→id=1
,先锁定id=2
。 -
此时事务1等待事务2释放
id=2
,事务2等待事务1释放id=1
,形成死锁。
-
-
MySQL的锁机制特性
-
当使用
WHERE id IN (a, b)
时,MySQL内部会按主键升序排序后再加锁(即使SQL中顺序不同)。 -
但若事务中存在多个独立的UPDATE语句(非批量更新),每个语句单独执行,加锁顺序由代码逻辑决定,可能不一致。
-
解决方案:强制统一加锁顺序
扣减库存前将商品根据ID升序排序再执行扣减库存操作 注意 ID升序排序 ID升序排序 ID升序排序
为什么必须升序排序?
-
MySQL内部机制:对
IN
列表中的主键,默认按升序排序后加锁(无论SQL写法如何)。批量操作 UPDATE goods set {字段}={值} where id IN(2,1)时mysql底层 就是主键升序排序执行 即便 修改sql: UPDATE goods set {字段}={值} where (select id from goods where id IN(1,2) order by id desc) 时 依旧无法改变升序排序 说到这里有同学可能会问 那:UPDATE goods
SET `num` = CASE
WHEN id = 2 AND `num` > 1 THEN `num` - 1
WHEN id = 1 AND `num` >= 2 THEN `num` - 2
ELSE `num`
END
WHERE id IN (1, 2); 的排序不可以吗 ? WHEN...THEN 写法不适合扣减库存使用 排序是解决了 如果库存不够 依然执行(这有个骚操作 ELSE `num` 改为字符串 让它抛出异常) 但是如果其它业务 需要批量操作 排序又要冲突 所以最好使用升序排序 -
人为排序保证一致性:显式排序确保代码逻辑与MySQL行为一致,避免隐式依赖。
总结
-
死锁根因:事务对多行记录加锁顺序不一致 → 循环等待。
-
终极解法:统一按主键升序访问记录。
-
扩展优化:乐观锁、事务重试、缩短事务时间。
其他优化建议
-
锁超时与重试机制
-
设置
innodb_lock_wait_timeout
控制锁等待时间。 -
代码层捕获死锁异常(如
1213
错误),自动重试事务。
-
-
减少事务粒度
-
尽量缩短事务执行时间,避免长事务持有锁过久。
-
-
乐观锁辅助校验
-
在商品表中增加
version
字段,更新时校验版本号:sql:UPDATE goods SET num = num - 1, version = version + 1 WHERE id = 1 AND version = @old_version;
-
-
隔离级别权衡
-
使用
READ COMMITTED
隔离级别可减少间隙锁范围,降低死锁概率。
-