MySQL 8.0 的索引跳跃扫描(Index Skip Scan)仅在联合索引最左列基数极低(如≤10–20个唯一值)、查询跳过该列且后续列选择性高、优化器估算成本更低时自动触发,本质是枚举前导列各值并分别执行范围扫描,非强制可用,也不适用于JOIN/GROUP BY等场景。

MySQL 8.0 的索引跳跃扫描(Index Skip Scan)不是“打开就自动生效”的功能,它只在特定条件下由优化器自主触发,不能强制指定某条 SQL 使用它。盲目依赖跳跃扫描,反而可能掩盖真正该建的索引。
什么时候会触发 skip_scan?
优化器判断是否启用跳跃扫描,核心看三点:
- 联合索引最左列(即跳过的那列)基数必须很低 —— 通常 ≤ 10~20 个不同值(比如
gender、status、is_deleted) - 查询条件中用到的后续列(如第二列、第三列)选择性要高(比如
city='Beijing'或create_time > '2025-01-01') - 执行计划中
EXPLAIN的type显示为range或ref,且Extra出现Using index for skip scan
注意:skip_scan 不适用于多表 JOIN、GROUP BY、DISTINCT,也不支持覆盖索引回表路径之外的复杂访问模式。
optimizer_switch 开关怎么调?
默认是开启的,但你得确认它没被意外关闭:
SHOW VARIABLES LIKE 'optimizer_switch';
输出中应包含 skip_scan=on。如果没开,用以下命令启用:
SET SESSION optimizer_switch='skip_scan=on';
⚠️ 不建议全局开启(SET GLOBAL),因为低基数假设不成立时,跳跃扫描反而比全表扫描更慢 —— 它本质是“枚举首列所有值 + 对每个值做子范围扫描”,首列有 1000 个值,就会拆成 1000 次小范围扫描。
为什么 EXPLAIN 看不到 Using index for skip scan?
常见原因有这些:
- 首列基数太高:比如
user_id或order_no这类高唯一性字段放在联合索引最左,优化器直接放弃跳跃扫描 - 查询用了
OR、LIKE '%xxx'、函数或隐式类型转换,导致索引无法按预期使用 - 表数据量太小(比如
- 统计信息过期:
ANALYZE TABLE t;可刷新行数与基数估算 - 使用了
FORCE INDEX,显式压制了优化器对skip_scan的选择权
验证是否真走跳跃扫描,别只看 key 字段,重点盯 Extra 列 —— 它才是最终判决书。
比 skip_scan 更可靠的做法是什么?
跳跃扫描是兜底策略,不是设计原则。真正稳定的解法永远是:
- 把高频查询的非首列,挪到联合索引靠前位置(例如把
(status, city, create_time)改为(city, status, create_time)) - 为该列单独建索引(如果写入压力可接受,且该列查询频次足够高)
- 用隐藏索引(
INVISIBLE)先上线测试新索引效果,再决定是否启用 - 对低基数列 + 高频等值查询场景,考虑用分区表(
LIST分区)替代跳跃扫描
跳跃扫描容易被忽略的一点是:它不减少索引维护开销 —— 你依然要为那个联合索引付出写放大代价,而它只在读时偶尔帮你省点事。











