INDEX SKIP SCAN 仅在MySQL 8.0.13+中启用,需同时满足:InnoDB表、联合索引≥2列、前导列低基数(如distinct率<1%)、查询跳过前导列且仅用后续列、无GROUP BY/DISTINCT、条件为等值或范围查询,且优化器评估成本最低时才生效。

MySQL 8.0.13+ 的 INDEX SKIP SCAN 确实能缓解“只查联合索引第二列却无法走索引”的痛点,但它不是自动生效的魔法开关——它只在特定数据分布和查询结构下被优化器选中,且极易被误判或压制。
什么情况下 INDEX SKIP SCAN 才可能启用
核心前提不是“语法合法”,而是优化器评估后认为它比其他路径更优。触发需同时满足:
- 表引擎必须是
InnoDB(MyISAM完全不支持) - 联合索引至少两列,且最左列(前导列)的
COUNT(DISTINCT col) / COUNT(*)比值要低(通常建议 status 只有 'active'/'inactive'/'pending') - 查询条件中**完全不出现前导列**,且后续列只能用等值(
=或IN),不能是范围(>、BETWEEN)、模糊(LIKE)或函数包裹(如YEAR(created_at)) - 没有
OR、IS NULL、隐式类型转换(如VARCHAR列传入数字)等禁用条件
如何确认你的查询真走了 SKIP SCAN
别只看 EXPLAIN 的 key 字段——它可能显示用了索引,但实际是全索引扫描(type: index)。关键信号只有两个:
-
type列必须是index_skip_scan(唯一明确标识) -
Extra字段含Using index for skip scan(注意不是Using index condition)
更可靠的方式是用:EXPLAIN FORMAT=JSON SELECT ...,检查输出中是否存在 "skip_scan": true 节点;或者观察慢查询日志里的 Rows_examined 是否显著低于全表行数,且 key 显示的是复合索引名。
为什么加了索引却没走 SKIP SCAN?常见硬伤
多数人卡在这一步,不是不会写 SQL,而是忽略了底层成本决策逻辑:
- 统计信息过期:大批量写入后未执行
ANALYZE TABLE table_name,导致优化器误判前导列基数 - 前导列实际基数太高:比如
(user_id, created_at)中user_id几乎唯一,优化器宁可走主键再回表,也不愿做几百万次“跳跃” - 用了
FORCE INDEX:该提示会绕过SKIP SCAN的候选路径评估 - MySQL 版本太老:8.0.13–8.0.20 存在多个漏判 bug,升级到 8.0.21+ 更稳定
- 查询带
ORDER BY或GROUP BY后续列:如WHERE b = ? ORDER BY b,SKIP SCAN不提供有序输出,优化器直接弃用
SKIP SCAN 性能不稳时的务实替代方案
当 SKIP SCAN 不稳定或开销反超时,优先考虑更确定的手段:
- 为高频查询的非首列单独建单列索引(如
(a,b)索引下常查b,直接加INDEX(b))——简单、可控、覆盖所有 MySQL 版本 - 改写查询,显式补上前导列的已知值(如
WHERE a IN ('active','inactive') AND b = ?),让优化器回归传统等值查找路径 - 确认是否真需要回表:若只需查索引列本身(如
SELECT b FROM t WHERE b = ?),确保索引是覆盖的(如INDEX(a,b)),否则回表 + 跳跃双重开销可能拖垮性能
真正容易被忽略的是:跳跃扫描省的是 I/O,不是 CPU;它把一次大扫描拆成 N 次小扫描,N 就是前导列不同值的数量。如果这个 N 是 5,效果明显;如果是 5000,它大概率会被优化器主动放弃——这时候你得先问自己:这个前导列的设计,真的合理吗?


















