mysql 8.0索引跳跃扫描不是万能补丁,仅在innodb表、前导列低基数(≤10–20个唯一值)、查询跳过前导列且仅用后续列等值条件、优化器估算成本更低时自动触发,无法强制启用,也不适用于join/group by/order by等场景。

MySQL 8.0 的索引跳跃扫描(Index Skip Scan)**不是万能补丁,只在特定数据分布下生效**。它不会让任意缺失最左列的查询变快,而是依赖前导列(联合索引最左列)的低基数(distinct 值极少)这一硬条件。盲目期待它“自动优化”反而会掩盖真正该建的单列索引。
确认是否满足跳扫触发前提
跳扫不是默认就开的“魔法开关”,它必须同时满足以下四点,缺一不可:
- 表引擎是
InnoDB(MyISAM完全不支持) - 查询条件中**只包含联合索引后缀列的等值条件**,且**完全不出现最左列**(例如索引是
(status, created_at),查询只能是WHERE created_at = '2024-01-01';若写了status = 'active' AND created_at = ...,就走普通最左前缀,不触发跳扫) - 最左列的
COUNT(DISTINCT)值必须足够小——通常建议 ≤ 10~20,比如gender、order_status('pending'/'shipped'/'cancelled')、is_deleted等枚举型字段 - 优化器估算跳扫总代价(≈
COUNT(DISTINCT 最左列)× 单次子扫描成本)低于全表扫描或其它路径;若统计信息过期,它可能直接放弃
用 EXPLAIN FORMAT=TREE 验证是否真在跳
仅看传统 EXPLAIN 的 type 或 key 字段不够,必须用树形格式才能看到核心线索:
- 执行
EXPLAIN FORMAT=TREE SELECT * FROM t WHERE b = 5; - 如果看到输出里有
-> Index skip scan或using_index_skip_scan字样,说明跳扫已启用 - 如果只显示
type: ALL或type: index且key为空,说明没触发——别猜,先查SELECT COUNT(DISTINCT a) FROM t;看前导列实际有多少不同值 - 注意:
USE INDEX (idx_ab)只强制用索引,**不能强制跳扫**;FORCE INDEX同理无效
跳扫性能陷阱比想象中更常见
它看起来是“一次走索引”,实则是“N 次小范围索引扫描”,I/O 放大效应明显:
- 前导列 distinct 值从 3 跳到 100,实际磁盘随机读次数就翻了 30 多倍,很可能比全表扫描还慢
- 无法用于
ORDER BY b或GROUP BY b—— 因为每次子扫描结果是局部有序的,合并后不保证全局有序 - 如果查询涉及回表(比如
SELECT *但索引不覆盖所有字段),每次子扫描都要回主键取数据,放大延迟 - 隐式类型转换会让整个跳扫失效:比如
b是VARCHAR,但传入数字WHERE b = 123,MySQL 会转成字符串再比较,导致索引无法对齐
什么情况下该放弃跳扫,直接建单列索引?
跳扫是兜底方案,不是设计目标。以下情况请立刻建真实索引:
- 业务中
WHERE b = ?查询高频且稳定——直接加INDEX (b),零成本、无歧义、无基数限制 - 前导列虽然只有几个值,但表写入极频繁(如日增百万行),跳扫带来的多次 B+ 树定位会加剧锁竞争和 CPU 开销
- 你发现
ANALYZE TABLE t后跳扫仍不启用,且COUNT(DISTINCT a)明显偏高(比如 > 50),说明数据分布已超出跳扫舒适区 - 需要配合
ORDER BY b分页,而跳扫无法提供有序性,此时单列索引 +ORDER BY b才是正解
最易被忽略的一点:跳扫对 NULL 值敏感。如果最左列大量为 NULL,MySQL 会把所有 NULL 当作一个独立值处理,但统计信息可能不准,导致优化器误判。上线前务必用真实数据集验证,而不是只看测试表的几条模拟数据。











