非索引字段的UPDATE不会加表锁,而是因全表扫描对每条聚簇索引记录加next-key锁(记录锁+间隙锁),效果等同锁全表;EXPLAIN显示type=ALL即表明已触发该高危行为。

非索引字段的 UPDATE 不会真正加表锁,但会为每条扫描到的主键记录加 next-key lock,效果等同于锁全表——这不是配置问题,是 InnoDB 在无法精确定位时的必然行为。
为什么 EXPLAIN 显示 type=ALL 就等于高危
当 EXPLAIN 的 type 字段为 ALL,说明 MySQL Server 层决定全表扫描;InnoDB 引擎层随之对每一行聚簇索引记录加 record lock + gap lock。哪怕 WHERE 只匹配 1 行,锁住的仍是全部主键值区间。
-
key为NULL且rows接近表总行数 → 锁范围已爆炸 -
filtered值低于 10 → 条件过滤效率极差,优化器大概率放弃索引 - 隐式转换(如
WHERE user_id = '123',而user_id是INT)不会报错,但会让key变成NULL
强制索引(FORCE INDEX)根本没用
FORCE INDEX 只影响执行计划选择,不解决“索引字段不覆盖 WHERE 条件”的本质问题。对 WHERE name = 'alice'(name 无索引),写 FORCE INDEX (idx_created) 会被优化器直接忽略,甚至报错。
- 单列索引用于多条件查询(如
WHERE status = ? AND created_at > ?)→ 若只建INDEX (created_at),仍会全扫 - 联合索引顺序错(建了
(created_at, status)却查status = ? AND created_at > ?)→ 最左前缀失效,退化为索引全扫描 - 函数操作字段(如
WHERE DATE(create_time) = '2024-01-01')→ 索引完全不可用,必须改写为范围查询
真正有效的索引策略
索引不是越多越好,而是要让 WHERE 中的等值条件字段在联合索引最左侧,范围条件字段紧随其后。例如查询 WHERE category = 'book' AND created_at > '2024-01-01',必须建 INDEX (category, created_at)。
- 高选择性字段优先:比如
order_no、user_id比status更适合作为联合索引首列 -
UPDATE ... LIMIT N不减少锁范围:WHERE 仍全表扫描,LIMIT只在最后截断更新行数 - 建完索引后必须再跑一次
EXPLAIN UPDATE ...(MySQL 8.0.19+ 支持),确认type不是ALL、key非NULL
线上紧急止血和长期预防
KILL 掉卡住的线程只是临时缓解,下一条相同语句立刻复现。关键得确认两点:这条 SQL 是否真的需要锁这么多行?对应字段有没有漏建索引?
- 查实时锁状态:
SELECT * FROM performance_schema.events_statements_history WHERE sql_text LIKE '%UPDATE%' AND rows_affected > 1000 - 开
log_queries_not_using_indexes = ON,配合long_query_time = 0捕获所有未走索引的写操作 - 应用层避免字符串拼接参数(如
"WHERE name = '" + input + "'"),否则预编译失效,索引也白建
最容易被忽略的是隐式转换和字符集混用——它们不报错、EXPLAIN 看不出异常,但锁范围会无声扩大到整张表。上线前必须用真实参数值跑一遍 EXPLAIN,而不是只测空值或默认值。











