innodb对非索引where条件执行update/delete时,会全表扫描聚簇索引并逐行加x锁,锁住所有扫描记录,等效锁表且易发死锁。

不走索引的 UPDATE 或 DELETE 语句不会直接“锁全表”,但会扫描整张聚簇索引并逐行加 X 锁——效果等同于锁表,且极易引发死锁。
WHERE 条件没走索引时,InnoDB 实际怎么加锁?
InnoDB 的行锁是“基于索引实现”的。一旦 WHERE 条件无法命中任何索引(或索引失效),优化器就会选择全表扫描:从聚簇索引(即主键 B+ 树)最左叶节点开始,挨个读取每条记录,并对每条匹配/扫描过的记录加 X 锁(排他锁)。这不是“升级”,而是根本没机会用行锁粒度控制——它被迫锁住所有扫描路径上的行。
常见错误现象:
- 明明只改
WHERE name = 'Alice',却导致其他事务更新id = 10000、id = 999999都被阻塞 -
SHOW ENGINE INNODB STATUS\G中看到RECORD LOCKS的heap no范围极大,甚至覆盖整页 - 死锁日志里显示两个事务锁住的
LOCK_DATA是大片连续主键值(如id=1..1000),而非离散点
哪些操作会让索引“看起来走了”,实则无效?
EXPLAIN 显示 type=range 或 ref,不代表 UPDATE 加锁安全。真正决定锁范围的是执行时是否能**精确定位物理行**。
以下情况都会让索引在加锁阶段“掉链子”:
-
WHERE user_id = '123':字段是INT,传字符串触发隐式转换,索引失效 -
WHERE UPPER(name) = 'ALICE':函数包裹索引列,无法做索引查找 -
WHERE created_at > '2026-01-01'但联合索引是(status, created_at):不满足最左前缀,该索引基本不用 -
ANALYZE TABLE长期未跑,统计信息过旧,优化器误判“全表扫描更快”,主动放弃索引
如何验证 UPDATE 是否真的走索引并控制锁范围?
不能只信 EXPLAIN SELECT。必须交叉验证执行计划、锁视图和死锁日志:
- MySQL 8.0+ 运行
EXPLAIN FORMAT=TRADITIONAL UPDATE ...,重点看key是否非NULL、rows是否接近实际匹配数 - 若
Extra出现Using where; Using index condition但key为空,说明索引未生效 - 事务中执行
UPDATE后不提交,立刻查SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS,观察LOCK_DATA是否大量非预期主键值 - 开启
innodb_print_all_deadlocks = ON,从 error log 看锁住的记录 ID 分布:离散 → 安全;连续大片 → 已锁扩大
为什么“锁扩大”比“慢查询”更危险?
全表扫描加锁不是性能问题,是并发破坏问题。它把原本可并行的多个小事务,强行串行化到同一张表的全部行上——一个慢 UPDATE 就可能堵住后续所有 DML。更麻烦的是,这种锁范围不可控:你无法预估它会锁多少行,也无法靠 LIMIT 限制(LIMIT 在加锁之后才生效)。修复时若只优化 SQL 而忽略锁行为验证,很容易反复踩坑。











