mysql 8.0.13+ 的 index skip scan 能在跳过联合索引首列(如只查 (status, created_at) 中的 created_at)时走索引,但需同时满足:innodb 表、前导列低基数(distinct率

MySQL 8.0.13+ 的 INDEX SKIP SCAN 确实能让你在只查联合索引第二列(比如 (status, created_at) 上查 created_at > '2025-01-01')时走索引,但**它不会自动生效,也不该被当作“补救索引设计失误”的万能解药**——能否触发,完全取决于数据分布和优化器的成本估算。
怎么确认你的查询真的走了 Skip Scan?
别只看 EXPLAIN 的 key 字段是否显示索引名。关键信号只有两个:
-
type列必须是index_skip_scan(不是range、ref或index) -
Extra列出现Using index for skip scan(注意不是Using index condition) - 用
EXPLAIN FORMAT=JSON查skip_scan节点是否存在,比传统格式更可靠
如果只看到 key 用了索引但 type 是 index,说明 MySQL 正在做全索引扫描,不是跳扫。
MySQL 9.6.0是面向Linux平台的2026年创新版本,核心架构迎来重大革新。其将外键约束与级联操作从InnoDB引擎层上移至SQL层,确保所有数据变更均被完整记录至Binlog,彻底解决了CDC(变更数据捕获)与主从复制中的数据不一致难题。此外,该版本引入container_aware启动选项以原生适配容器环境,并对审计日志进行了组件化重构,为追求极致数据一致性与云原生体验的开发者提供了全新选择。
为什么明明建了 (a,b) 索引,WHERE b = ? 却没触发?
最常见原因不是语法错,而是优化器压根没选这条路。几个硬性卡点:
- 前导列
a的基数太高:比如COUNT(DISTINCT a) / COUNT(*) > 0.01(即唯一值占比超 1%),优化器大概率放弃——它要枚举的子扫描次数太多,成本反超全表扫描 - 前导列含大量
NULL:MySQL 会跳过NULL值做探测,若a列 90% 是NULL,实际只对剩余 10% 值扫描,但统计信息可能不准,导致误判 - 查询里混了
OR、IS NULL、函数(如YEAR(created_at))、隐式类型转换(varchar列传入数字)——这些直接禁用跳扫 - 表刚批量写入过但没跑
ANALYZE TABLE:统计信息过期,优化器基于错误基数估算,宁可选错路也不选跳扫
如何让 Skip Scan 更大概率被选中?
这不是调开关就能解决的事,得从数据和查询两端下手:
- 先验证前导列真实基数:
SELECT COUNT(DISTINCT a), COUNT(*) FROM t;—— 如果结果是5和1000000,那它大概率符合条件 - 确保
optimizer_switch包含skip_scan=on(默认开启,但得检查:SHOW VARIABLES LIKE 'optimizer_switch';) - 避免
FORCE INDEX:它会绕过优化器的跳扫决策逻辑 - 覆盖查询优先:如果只需查
b和a,把SELECT *改成SELECT a, b,配合(a,b)索引形成覆盖,跳扫的 I/O 优势才能真正体现 - 别指望它优化
ORDER BY b或GROUP BY b:跳扫不提供有序输出,排序/分组仍需额外操作
真正容易被忽略的是:Skip Scan 不是“跳过前导列”,而是“枚举前导列所有值再分别扫”。前导列有 500 个不同值,它就执行 500 次小范围索引查找——这比建一个单列 (b) 索引更重,也更不确定。所以,当性能关键且查询稳定时,单列索引仍是首选;跳扫更适合低频、临时、或无法改索引的历史表场景。










