MySQL 8.0 索引跳跃扫描:老表不用改结构也能吃到红利
接手了一个 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 的理由清单里,这一条值得加上。
- 点赞
- 收藏
- 关注作者
评论(0)