确认走index skip scan的唯一可靠标识是explain中type为index_skip_scan且extra含using index for skip scan;或format=json中存在"skip_scan": true节点。

MySQL 8.0.13+ 默认支持索引跳跃扫描,但它不会自动让所有“跳过最左列”的查询变快——是否启用完全取决于前导列的基数、查询结构和优化器成本估算。
怎么确认你的查询真走了 Index Skip Scan?
别只看 EXPLAIN 的 key 字段是否显示索引名,那只能说明用了索引,不能说明是跳扫。关键要看执行计划里的明确信号:
-
EXPLAIN FORMAT=TREE输出中出现Index skip scan或using_index_skip_scan -
EXPLAIN FORMAT=JSON输出里有"skip_scan": true - 传统
EXPLAIN中:type列为index_skip_scan(唯一可靠标识),且Extra含Using index for skip scan - 如果
Extra是Using index condition或空,type是index或ALL,那就没走跳扫
为什么加了复合索引却没触发跳扫?常见硬伤
不是语法写错了,而是优化器主动放弃了。典型原因包括:
- 前导列实际
COUNT(DISTINCT a)过高(比如 > 20),优化器算下来要拆成几十次小扫描,I/O 开销反而比全表扫描大 - 统计信息过期:大批量写入后没执行
ANALYZE TABLE t,导致优化器误判前导列基数 - 查询条件含隐式类型转换,例如
b是VARCHAR,但写了WHERE b = 123,索引无法对齐 - 用了
FORCE INDEX或USE INDEX—— 它们只强制用哪个索引,不控制访问方式,跳扫仍由优化器决定 - 条件里混了
OR、LIKE '%xxx'、函数(如YEAR(created_at))或IS NULL,直接禁用跳扫路径
如何让跳扫更可能被选中?
这不是“调参就能开”的功能,而是靠数据特征和查询写法共同满足条件:
- 确保表引擎是
InnoDB(MyISAM完全不支持) - 前导列必须是低基数字段,比如
status('active'/'inactive'/'pending')、gender、is_deleted,且COUNT(DISTINCT a) / COUNT(*) - WHERE 条件里**完全不出现前导列**,只用后续列做等值(
=或IN),不能是范围(>、BETWEEN)、模糊或函数 - 检查
SELECT @@optimizer_switch,确认含skip_scan=on(默认开启,但可能被会话级关闭) - 避免在高频查询场景下依赖跳扫——它本质是“枚举前导列每个值 + 分别查”,前导列有 100 个值,就是 100 次随机 I/O
比跳扫更靠谱的替代方案是什么?
跳扫是兜底手段,不是设计依据。真正稳定的做法永远更直接:
- 把高频查询的列提到联合索引前面,比如原索引是
(status, city, create_time),而你常查city = ?,就该改成(city, status, create_time) - 为该列单独建单列索引(只要写入压力允许),这是最简单、最可控的解法
- 用覆盖索引减少回表,比如
SELECT city FROM t WHERE create_time > ?,可建(create_time, city)索引
跳扫容易被忽略的一点是:它不保证结果有序。哪怕你在 (a,b) 索引上查 b = ?,返回行顺序仍是按 a 分组后局部有序,合并后无全局顺序——所以不能用于 ORDER BY b 或 GROUP BY b。











