UPDATE语句必须加索引条件,否则InnoDB会升级为表级意向锁+逐行加锁,高并发下极易形成环形等待死锁;需确保WHERE字段有单列或联合索引并满足最左前缀原则,事务内多条UPDATE须全局统一顺序,避免长事务和跨服务强一致性依赖。

UPDATE 语句必须加索引条件,否则全表锁是死锁温床
MySQL 的 UPDATE 如果没走索引,InnoDB 会升级为表级意向锁+逐行加锁,多个事务交叉扫描时极易形成环形等待。这不是“可能”,而是高并发下几乎必然触发。
- 错误写法:
UPDATE users SET status = 1 WHERE name = 'alice'(name字段无索引) - 正确做法:确保
WHERE条件中所有字段都有对应单列或联合索引,用EXPLAIN验证type是ref或更优 - 特别注意:联合索引有最左前缀要求,
WHERE a = ? AND b = ?能用(a,b)索引,但WHERE b = ?就失效 - 线上加索引要评估
ALTER TABLE阻塞风险,优先用ALGORITHM=INPLACE和低峰期操作
事务内多条 UPDATE 的执行顺序必须全局一致
死锁最常见原因不是锁本身,而是不同事务以不同顺序访问相同资源。比如事务 A 先改订单再改库存,事务 B 反过来,两秒内就卡死。
- 业务层必须约定「资源访问顺序」:例如统一按「用户 → 订单 → 商品 → 库存」层级更新
- 避免在事务里拼接动态 SQL 导致顺序不可控,比如根据参数决定先更新 A 还是 B
- 如果涉及跨微服务资源(如订单服务调库存服务),本地事务只能管住自己那部分,外部依赖需用 Saga 或补偿逻辑,不能靠锁硬扛
- 用
SELECT ... FOR UPDATE时也要遵守同一顺序,否则照样死锁
长事务是死锁放大器,必须拆解或设超时
一个持续 5 秒的事务,持有锁的时间就是 5 秒——期间所有想碰同一行的请求都在排队,冲突窗口成倍扩大。
- 检查
innodb_lock_wait_timeout(默认 50 秒),但别只依赖它;应在应用层主动控制事务边界 - 把「查+算+改」拆开:先
SELECT拿数据,业务逻辑在事务外跑完,再进短事务UPDATE - 避免在事务里调外部 HTTP、读大文件、做复杂计算——这些不产生锁,但拖长锁持有时间
- 监控
information_schema.INNODB_TRX表,定期查trx_started时间过长的事务,定位问题代码
INSERT ... ON DUPLICATE KEY UPDATE 容易隐式锁升级
这个语法看着原子,实际分三步:尝试插入 → 冲突则更新 → 更新时对冲突行加 X 锁。若多个事务同时插入相同唯一键值,会争抢同一行的锁,且顺序难预测。
- 尤其注意
REPLACE INTO,它本质是DELETE + INSERT,锁范围更大、耗时更长,比ON DUPLICATE KEY UPDATE更容易引发死锁 - 如果业务允许,优先用普通
INSERT+ 捕获1062 Duplicate entry错误,再走单独UPDATE,把锁粒度和时机完全可控 - 唯一索引字段越多,冲突概率越高;复合唯一索引要确认业务是否真需要,有时冗余索引反而是死锁源头
真正难防的不是单条 SQL 写错,而是多个模块在不同时间点、不同上下文里各自“合理”地加锁,最后在某个并发瞬间咬死。业务逻辑层的协作约定,比任何数据库配置都关键。











