索引失效不直接导致锁阻塞,而是使查询退化为全表/全索引扫描,导致InnoDB对所有扫描记录加锁。根本原因是锁范围扩大,而非索引本身失效。

索引失效本身不直接导致锁阻塞,但它会让 UPDATE 或 SELECT ... FOR UPDATE 退化为全表/全索引扫描,InnoDB 就不得不对所有扫描到的聚簇索引记录加锁——这才是并发更新卡住的真正原因。
为什么EXPLAIN显示“走了索引”,但锁还是满天飞?
常见错觉是只要 EXPLAIN 的 key 列非 NULL、type 是 range 或 ref,就以为锁很轻。但 InnoDB 加什么锁、锁多少行,取决于:
- 是否命中唯一索引(如主键、
UNIQUE)→ 只锁单行 - 是否用非唯一二级索引 + 无覆盖 → 先锁索引项,再回表,可能触发间隙锁(
Gap Lock) - 查询条件含
IS NULL、!=、NOT IN、OR中混入非索引列 → 可能退化为全范围扫描加锁 - 事务隔离级别为
REPEATABLE READ(默认)→ 自动启用间隙锁防幻读
例如:UPDATE t_award SET status = 1 WHERE pool_id = 123 AND identifier IS NULL,即使 pool_id 有索引,identifier IS NULL 无法走索引,InnoDB 可能对整个 pool_id = 123 区间加间隙锁——这就是“索引部分生效但锁爆炸”的典型。
怎么确认是不是索引失效放大了锁范围?
别只信 EXPLAIN,要结合运行时锁视图验证:
- 对报错 SQL 执行
EXPLAIN,重点看type是否为ALL或index,key是否为NULL,rows是否远超预期(比如查 1 行却扫 50 万行) - 立刻查
performance_schema.data_locks,过滤LOCK_TYPE = 'RECORD',观察LOCK_DATA是否大量重复或跨度极大(如从1到5000) - 对比
INFORMATION_SCHEMA.INNODB_TRX中trx_rows_locked字段——若远高于实际匹配行数,基本可断定索引没生效
注意:SHOW ENGINE INNODB STATUS\G 里的锁信息易被覆盖,仅作辅助;真正可靠的证据链是 EXPLAIN + data_locks + INNODB_TRX 三者交叉印证。
WHERE里含IS NULL、!=、函数等,怎么避免锁扩大?
IS NULL 在 B+ 树中特殊处理,很难利用索引定位;!= 和函数(如 DATE(create_time))会直接让优化器放弃索引。修复核心是:让条件可索引化。
- 把字段改为
NOT NULL DEFAULT '',业务层用空字符串代替NULL - MySQL 8.0+ 可建函数索引:
CREATE INDEX idx_identifier_null ON t_award ((identifier IS NULL)) - 更稳妥:加生成列标记状态再索引:
ALTER TABLE t_award ADD COLUMN is_identifier_null TINYINT AS (CASE WHEN identifier IS NULL THEN 1 ELSE 0 END) STORED,然后建复合索引:CREATE INDEX idx_pool_null_status ON t_award (pool_id, is_identifier_null, status, is_redeemed) - 原查询改写为:
WHERE pool_id = ? AND is_identifier_null = 1 AND status = 0 AND is_redeemed = 0,完全走索引,锁精准到匹配行
隐式类型转换(如 phone = 13800138000,而 phone 是 VARCHAR)和 LIKE '%xxx' 同理——必须统一类型、避免前导通配符,否则索引形同虚设。
复合索引顺序怎么排,才能既查得快又锁得准?
不是“高频字段放前面”就够——要按 过滤强度 + 确定性 排序:
- 高区分度、等值查询、非
NULL字段优先(如user_id比status更适合作首列) -
IS NULL类模糊条件不能放最左,应转为确定性生成列后再参与排序 - 范围查询字段(如
created_at > ?)尽量靠后,避免截断索引使用 - 如果常查
WHERE a = ? AND b > ? AND c = ?,索引应为(a, c, b),而非(a, b, c)—— 因为b是范围,后面字段无法走索引
最容易被忽略的一点:索引设计必须和锁行为对齐。一个“能走索引”的查询,如果落在非唯一二级索引上且未覆盖,仍可能因回表引发额外间隙锁;而一个“看似低频”的字段,只要它能把结果集压缩到个位数,就值得放在索引前列——锁粒度比查询速度更关键。


















