是。索引失效会导致锁范围扩大,因innodb退化为全表或全索引扫描,对所有扫描到的聚簇索引记录加next-key lock或gap lock,引发并发阻塞与死锁。

索引失效 和 锁算法不当 实际上是两个独立问题:前者影响查询走不走索引,后者影响并发时加什么锁、锁住多少行。但它们会交叉恶化——比如因索引失效导致全表扫描,InnoDB 就不得不加更多锁(甚至表级范围锁),进而引发死锁或性能雪崩。
真正需要修复的,不是“锁算法导致索引失效”,而是索引设计或使用不当,放大了锁冲突风险。下面直击关键点:
为什么 EXPLAIN 显示走了索引,但还是锁得厉害?
常见错觉:只要 key 字段非 NULL、type 是 ref 或 range,就以为锁很轻。但 InnoDB 的锁行为取决于:
- 是否命中**唯一索引**(如主键、
UNIQUE)→ 只锁单行 - 是否使用**非唯一二级索引 + 无覆盖** → 先锁索引项,再回表,可能触发间隙锁(
Gap Lock) - 查询条件是否包含
IS NULL、!=、NOT IN等 → 可能退化为全范围扫描加锁 - 事务隔离级别是否为
REPEATABLE READ(默认)→ 自动启用间隙锁,防止幻读
例如:SELECT * FROM t_award WHERE pool_id = 123 AND identifier IS NULL,即使 pool_id 有索引,identifier IS NULL 无法走索引,InnoDB 可能对整个 pool_id = 123 的索引区间加间隙锁——这就是“索引部分生效但锁爆炸”的典型。
WHERE 条件含 IS NULL 时如何避免锁扩大?
NULL 值在 B+ 树中特殊处理,IS NULL 很难利用索引定位,常触发范围扫描。修复核心是:**让 NULL 可索引化**。
- 把
identifier字段改为NOT NULL DEFAULT '',业务层用空字符串代替 NULL - 若必须保留 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_is_null 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,完全走复合索引,且锁精准到匹配行。
复合索引顺序怎么排,才能兼顾查询和锁粒度?
不是“高频字段放前面”就够——要按 **过滤强度 + 确定性** 排序。高区分度、等值查询、非 NULL 字段优先。
- 错误示例:
INDEX idx_pool_status (pool_id, status, is_redeemed, identifier)——status和is_redeemed只有 2–3 个取值,选择性差,后面字段基本失效 - 正确做法:把
pool_id(高基数)放最左,接着放确定性高的等值条件列,最后才是范围或 NULL 判断列。例如:INDEX idx_award_lookup (pool_id, is_redeemed, status, identifier),其中is_redeemed = 0 AND status = 0能快速收敛结果集 - 如果查询总带
identifier IS NULL,就把生成列is_identifier_null放进索引,且位置靠前
记住:InnoDB 对复合索引的使用严格遵循最左前缀,但**锁的范围由最终命中的索引条目区间决定**。越早过滤掉无关数据,锁就越窄。
为什么 UPDATE ... LIMIT 1 还会死锁?
很多人以为加了 LIMIT 1 就只锁一行——错。InnoDB 在执行前并不知道哪一行满足条件,它会先按索引顺序扫描,逐行加锁判断,直到找到第一个匹配项才停止。如果扫描路径不同(比如两个事务走不同索引),就极易形成循环等待。
- 确保该查询**永远走同一个确定性索引**,禁用优化器“偷懒”选错索引(用
FORCE INDEX) - 避免在高并发场景下依赖
LIMIT做“抢锁”,改用SELECT ... FOR UPDATE显式加锁,并配合应用层重试 - 检查是否有其他事务正在对该表做
INSERT或UPDATE涉及相同pool_id区间——间隙锁冲突是元凶
真正棘手的从来不是单条 SQL,而是多条语句在相同索引区间上以不同顺序加锁。索引失效只是导火索,锁机制才是爆点。











