update语句本身不会导致“全表死锁”,但where条件未走索引引发全表扫描、间隙锁蔓延且事务未提交时,会大面积阻塞并发操作,现象类似整表锁死。

UPDATE 语句本身不会导致“全表死锁”,但当它触发全表扫描 + 间隙锁蔓延 + 事务未提交时,其他并发操作会大面积阻塞——现象上就像整张表被锁死。
WHERE 条件没走索引,直接全表扫描加锁
这是最常见也最隐蔽的根源。InnoDB 的 UPDATE 在 WHERE 不命中索引时,会退化为全表扫描,对每行匹配记录加记录锁(Record Lock),同时在所有扫描间隙上补上间隙锁(Gap Lock)。哪怕只更新 1 行,只要没索引,就可能锁住上千行甚至整个主键范围。
- 检查方式:
EXPLAIN FORMAT=TRADITIONAL看type是否为ALL,rows是否接近表总行数 - 典型失效场景:
WHERE DATE(created_at) = '2026-08-01'(函数导致索引失效)、mark_id = 123(字段是VARCHAR却传数字,隐式转换) - 修复动作:给 WHERE 字段建索引;避免函数包裹;确认数据类型严格匹配
事务未提交,锁长期持有且自动升级
锁的生命周期由事务控制,不是 SQL 执行时间。一个 UPDATE 毫秒完成,但若它在长事务中(比如 Spring @Transactional 里混了 HTTP 调用或日志写入),锁就会一直挂着。更危险的是:InnoDB 在行锁数量超过阈值时会自动升级为页锁甚至表级意向锁,进一步放大阻塞面。
- 查长事务:
SELECT * FROM information_schema.INNODB_TRX,重点关注trx_started和trx_state = 'RUNNING' - 高风险操作:ORM 中事务内调用外部服务、循环中累积大量 UPDATE、忘记
COMMIT或异常未ROLLBACK - 临时缓解:
KILL掉长时间运行的trx_mysql_thread_id
多表操作加锁顺序混乱,形成跨表死锁环
所谓“全表死锁”常是误判——实际是多个事务交叉锁定不同表的行,导致一批并发请求集体卡住。例如事务 A 先锁 users 再锁 orders,事务 B 反过来,两者都卡在第二把锁上,MySQL 检测到死锁后只回滚其中一个,但剩下那个仍等不到资源,看起来像整张表不可用。
- UPDATE JOIN 尤其危险:优化器决定的加锁顺序不透明,
UPDATE t1 JOIN t2 ON ...可能先锁 t2,另一条相似语句却先锁 t1 - 正确做法:拆成单表语句,显式按固定顺序执行,例如先
UPDATE users ... WHERE id = ?,再UPDATE orders ... WHERE user_id = ? - 硬约束:所有多表变更逻辑必须按表名字母序(
accounts → orders → users)或业务主次序编码,不能靠条件分支动态决定
真正难排查的,从来不是那条报错的 UPDATE 语句本身,而是它背后没出现在日志里的执行计划选择、物化策略和锁升级行为——这些只能靠 EXPLAIN FORMAT=JSON 和死锁日志中的 resource-list 还原。











