MySQL 8.0 索引跳跃扫描:老表不用改结构也能吃到红利

举报
yd_275379215 发表于 2026/11/04 09:40:05 2026/11/04
【摘要】 接手了一个SaaS业务的库,表上有个联合索引idx_tenant_status(tenant_id,status)。业务方经常跑只按status过滤的统计SQL,在5.7上只能全表扫,表8000万行,一条报表查询要跑十几秒。按老办法要么加个status单列索引,要么改SQL强制带上tenant_id。升级8.0之后发现第三条路:索引跳跃扫描(SkipScan),不用加索引,查询直接从12秒降...

接手了一个 SaaS 业务的库,表上有个联合索引 idx_tenant_status(tenant_id, status)。业务方经常跑只按 status 过滤的统计 SQL,在 5.7 上只能全表扫,表 8000 万行,一条报表查询要跑十几秒。按老办法要么加个 status 单列索引,要么改 SQL 强制带上 tenant_id。升级 8.0 之后发现第三条路:索引跳跃扫描(Skip Scan),不用加索引,查询直接从 12 秒降到 0.9 秒。

原理其实朴素:联合索引的第一列 tenant_id 只有 300 多个取值,优化器把 where status=2 这种缺前导列的查询,改写成"枚举每个 tenant_id,在每个前缀下做二分定位"的 N 次区间扫描,N 就是前导列的 distinct 值个数。等于优化器替你补上了前缀。

看一个最小验证例子:

-- 表结构:联合索引前导列 tenant_id,低基数
ALTER TABLE t_workorder ADD INDEX idx_tenant_status(tenant_id, status);

-- 8.0.13+ 执行计划出现 Using index for skip scan
EXPLAIN SELECT COUNT(*) FROM t_workorder WHERE status = 2;

-- 确认开关(默认开启)
SELECT @@optimizer_switch LIKE '%skip_scan=on%';

-- 看真实执行形态
EXPLAIN FORMAT=TREE SELECT COUNT(*) FROM t_workorder WHERE status = 2;
-- => Index skip scan on t_workorder using idx_tenant_status

但有三个限制必须心里有数。第一,前导列基数要低,官方建议几百以内,如果前导列是 user_id 这种几十万取值,跳扫次数太多反而更慢,优化器一般会自己放弃。第二,只适用于单表的等值/范围条件,第二列上用 OR 连接复杂条件可能不吃。第三,它省的是加索引的空间成本,不是万能替身,核心高频查询该建专用索引还是建。

上线后的观察也要跟上:跳扫的收益随数据分布漂移,tenant_id 取值涨到上千后扫描次数线性上涨。我们加了一个月度检查,对比该查询的执行计划是否仍带 skip scan 标记、平均耗时是否劣化超过 30%,不达标就评估专用索引。红利要吃,但要有退出机制。

上线前我们用 8.0.35 做了对比:tenant_id 300 取值、status 5 取值的场景,跳扫比全表扫快 13 倍,但比专用单列索引慢 2 倍左右。最终方案是报表类低频查询吃跳扫红利,核心链路查询还是补了索引。老库升级 8.0 的理由清单里,这一条值得加上。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。