联合索引跳过最左列时,MySQL无法定位B+树起点,被迫全索引扫描并加全范围间隙锁,导致非业务预期的锁膨胀与插入阻塞。

联合索引跳过最左列时,MySQL无法定位索引起点
当查询条件没包含联合索引的最左列(比如索引是 (user_id, status, created_at),但只写 WHERE status = 1),InnoDB 就没法从 B+ 树根节点往下精准导航。它只能退化为全索引扫描——也就是遍历整个二级索引叶节点链表。这时,所有被扫描到的索引记录之间、以及首尾两端的“边界间隙”,都会被加上间隙锁。
非最左匹配触发全范围扫描,间隙锁覆盖整个索引区间
例如表 orders 有联合索引 (user_id, status, created_at),执行:
SELECT * FROM orders WHERE status = 1 FOR UPDATE;
由于 status 不是最左列,MySQL 实际会扫描该索引全部条目(哪怕只有几行满足 status = 1)。此时锁定的不只是匹配行之间的间隙,而是整个索引键空间的潜在间隙,比如:
- 从负无穷到第一个
user_id值之间的间隙 - 每两个相邻
(user_id, status, created_at)元组之间的间隙 - 最后一个元组到正无穷之间的间隙
这些间隙全被加锁,其他事务插入任何 user_id 值的新订单都会被阻塞——远超业务本意。
唯一索引等值查询不加间隙锁,但普通联合索引非最左匹配会
关键区别在于锁类型退化逻辑:
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
-
WHERE user_id = 100 AND status = 1→ 走索引前缀,只锁匹配行及紧邻间隙(Next-Key Lock) -
WHERE status = 1→ 无法利用索引结构,全扫 + 每个扫描到的索引项都可能触发 Gap Lock - 即使
status列本身有单列索引,联合索引也不会“自动降级”为那个单列索引使用
更隐蔽的是:如果该联合索引上 user_id 值分布稀疏(比如只有 1、1000、10000),那扫描过程实际覆盖的键范围极大,间隙锁范围也跟着膨胀。
如何快速验证当前查询是否引发大面积间隙锁
在事务中执行查询后,立刻查 INFORMATION_SCHEMA.INNODB_TRX 和 INFORMATION_SCHEMA.INNODB_LOCKS(MySQL 5.7+)或 performance_schema.data_locks(8.0+),重点关注:
-
LOCK_TRX_ID对应的事务是否长时间未提交 -
LOCK_MODE是否含GAP字样 -
LOCK_DATA显示的锁定值是否跨度异常大(如显示1000, 1, '2026-01-01'和10000, 1, '2026-12-31')
真正危险的不是“有没有间隙锁”,而是“间隙锁是否脱离业务语义地横跨了本不该干涉的数据段”。这种失控往往藏在看似无害的 WHERE status = ? 后面。










