InnoDB行锁加在索引记录上,WHERE条件无索引时触发全表扫描并为每条聚簇索引记录加记录锁+间隙锁,等效锁全表;强制索引无效,需建匹配查询模式的联合索引。

UPDATE WHERE 非索引字段为什么会锁全表
因为 InnoDB 的行锁不是加在“数据行”上,而是加在“索引记录”上。当 WHERE 条件无法命中任何索引时,优化器只能走 type = ALL 全表扫描,于是为**每一条被扫描到的聚簇索引记录**都加上记录锁(Record Lock)+ 间隙锁(Gap Lock),最终效果等价于锁住整张表——哪怕只有一行匹配,也会锁住所有主键值。
这不是 bug,是 InnoDB 在无法精确定位时的保守行为:宁可多锁,也不能漏锁,否则会破坏事务隔离性。
- 典型错误现象:
SHOW ENGINE INNODB STATUS里看到大量LOCK WAIT,State: Locked的线程,且Trx has been waiting时间持续增长 - 执行
EXPLAIN FORMAT=JSON后发现key字段为NULL、rows值远超业务预期(比如查 10 条却扫 50 万行)、filtered低于 10 - 隐式转换也会触发:比如
user_id是INT,但写成WHERE user_id = '123',MySQL 强制类型转换导致索引失效
强制使用索引能绕过锁全表吗
不能。用 FORCE INDEX 或 USE INDEX 只影响执行计划选择,不解决根本问题——如果该索引本身不覆盖 WHERE 条件字段,强制也没用;如果强制的是一个无效索引(比如单列 status 索引用于 WHERE status = ? AND created_at > ?),优化器仍可能拒绝使用,或即使用了也因不满足最左前缀而退化为索引全扫描,照样锁大量记录。
真正起作用的是「索引能否让 WHERE 条件精准截断扫描范围」。
-
FORCE INDEX (idx_created)对WHERE name = 'alice'(name无索引)完全无效,优化器直接报错或忽略 - 对范围条件如
created_at > '2024-01-01',即使有单列索引,也可能因高选择性不足导致扫描行数爆炸,锁范围依然很大 - 唯一有效路径是建联合索引,且顺序必须匹配查询模式:例如
WHERE a = 1 AND b > 2,应建INDEX (a, b),而非(b, a)
如何快速定位正在锁全表的 UPDATE 语句
别靠猜,查真实运行时数据。MySQL 5.7 的 performance_schema 和慢日志组合是最准的入口。
- 先开启诊断开关:
SET GLOBAL slow_query_log = ON,SET GLOBAL long_query_time = 0,SET GLOBAL log_queries_not_using_indexes = ON - 查高影响写操作:
SELECT * FROM performance_schema.events_statements_history WHERE sql_text LIKE '%UPDATE%' AND rows_affected > 1000 - 对可疑 SQL 跑
EXPLAIN FORMAT=JSON,重点盯三个字段:key是否为NULL、rows是否异常大、filtered是否低于 20 - 补充验证:在事务中执行该语句后,立刻查
SELECT * FROM information_schema.INNODB_TRX,看trx_rows_locked是否动辄几万+
加索引就能解决问题?这些副作用你得知道
加索引是解法,但不是无代价的银弹。尤其在写多读少的业务表上,盲目加索引可能把锁竞争从“表级”转移到“索引 B+ 树 latch”层面,反而更难排查。
- 单列索引对多条件查询基本无效,优先建联合索引,字段顺序按过滤性从高到低排(如
status区分度低,user_id高,则(user_id, status)比(status, user_id)更稳 -
LIKE '%abc'这种前导模糊查询,加 B+ 树索引完全没用,得换全文索引或倒排结构 - 高频
INSERT/UPDATE表上,每个额外索引都会拖慢写入速度,并增加INSERT时的插入意向锁(Insert Intention Lock)争抢 - 如果语句本身只是校验后更新(比如“查余额 ≥ 100 才扣减”),考虑先用
SELECT ... LOCK IN SHARE MODE读取再判断,比直接FOR UPDATE更轻量(注意幻读风险需配合事务隔离级别)
最麻烦的从来不是“要不要加索引”,而是加哪个、加在哪、加了之后是否真被用上——这三步缺一不可,漏掉任一环,锁表问题照旧。


















