PostgreSQL 18 Skip Scan 详解:不带前导列也能用多列索引
PostgreSQL 18 新增 Skip Scan 优化:查询只过滤多列 B-Tree 索引的非前导列时,优化器可按前导列逐值"跳跃"搜索,把全表扫描变成多次精准索引查找。本文讲清原理、触发条件、执行计划识别方法与五项限制。
**Skip Scan 解决一个老问题:复合索引 (status, created_at) 建好了,查询却只按 created_at 过滤——以前这种查询只能全表扫描或全索引扫描。**PostgreSQL 18 的优化器学会了"跳过"前导列:为前导列的每个取值生成动态等值条件,把一个查询拆成多次定向索引搜索,大表上提速显著。
原理:一次查询变 N 次精准搜索
假设 status 列只有 3 个取值(active / inactive / pending)。对于:
SELECT * FROM users
WHERE created_at > '2024-01-01'
ORDER BY created_at LIMIT 100;
优化器内部把它等价为:
-- 逻辑上等价于对每个 status 取值做一次完整索引搜索:
WHERE status='active' AND created_at > '2024-01-01'
WHERE status='inactive' AND created_at > '2024-01-01'
WHERE status='pending' AND created_at > '2024-01-01'
每次搜索都能利用完整复合索引直接跳到相关区段,掠过无关的索引部分。前导列基数(distinct 值个数)越低,这个策略越划算——3 个取值就是 3 次快速搜索,而不是扫全表。
全自动,无需配置
Skip Scan 没有任何开关参数,优化器基于统计信息和成本估算自动决定。你也无法强制启用——它只在被估算为最快方案时出场。
如何在执行计划里认出它
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM users
WHERE created_at > '2024-12-01'
ORDER BY created_at LIMIT 100;
关键信号是 Index Searches: N:
-> Bitmap Index Scan on idx_users_status_created
Index Cond: (created_at > '2024-12-01')
Index Searches: 4 -- ← 对前导列做了 4 次独立搜索
Index Searches: 4 表示优化器对前导列取值做了 4 次索引搜索(可能通过位图扫描节点实现,而非独立的 "Index Skip Scan" 节点)。实测中这类计划比并行全表扫描快数倍(示例数据 25ms vs 94ms)。
什么时候不会触发
同一个索引,查询换成 created_at > '2024-01-01'(命中全表 20% 行),优化器大概率选并行顺序扫描而非 Skip Scan。原因:
- 选择性差:结果集占全表 20% 时,并行扫描摊到多个 CPU 核上反而更快;
- 位图扫描更便宜:中等规模结果集下,先在内存建位图再按物理顺序取行可能更优;
- 小表:几千行的表,顺序扫描就是最快,索引遍历的开销都不值;
- 统计信息过期:刚大批量导入数据没跑
ANALYZE,优化器的成本估算会失真。大变更后记得:
ANALYZE users;
五项硬性限制
- 仅 B-Tree 索引:GiST、GIN、BRIN 等不支持——好在 B-Tree 是默认也是最常用的索引类型;
- 前导列要低基数:前导列有成千上万个取值时,Skip Scan 要执行同等次数的独立搜索,开销急剧累积,优化器会弃用。设计复合索引时,把低基数列放前面是能否吃到这个红利的关键;
- 非前导列必须有过滤条件:没有对后续列的条件,就没有可优化的目标;
- 支持等值与范围条件:
category = 'Electronics'或sale_date > '2025-01-01'都可以,但条件选择性直接影响是否被选中; - 成本决定一切:最终选择权在优化器,你只能通过索引设计、统计信息维护去"配合"它。
实战建议
- 审计现有复合索引:前导列基数高 + 业务常查后续列的组合是 Skip Scan 的理想候选;
- 反过来,如果你建复合索引就是为了查后续列,把低基数列放前导位;
- 升级后别急着加新索引——先
EXPLAIN看老查询是否已被 Skip Scan 救活。
观测云对照
索引策略调整的效果要用慢查询数据验证。观测云 DataKit 采集 PostgreSQL 慢查询与性能指标,升级 18 或调整索引后,在仪表板对比目标 SQL 的执行耗时变化;用监控器给慢查询率设告警,防止执行计划因数据分布变化悄悄劣化。 1 2
常见问题(FAQ)
Q:Skip Scan 需要手动开启吗?
不需要,也没有任何开关。PostgreSQL 18 起优化器自动评估使用,升级即得。
Q:怎么知道我的查询用上 Skip Scan 了?
EXPLAIN (ANALYZE, BUFFERS) 查看计划,找 Index Searches: N(N>1)字样,或独立的 Index Skip Scan 节点。
Q:前导列多少个 distinct 值算"低基数"?
没有硬阈值,经验值是几十以内收益明显,几百以上优化器基本不会选。最终由成本估算说了算。
Q:Skip Scan 和直接给后续列建单列索引哪个好?
单列索引只服务一种查询;带低基数前导列的复合索引 + Skip Scan 能同时服务"带前导列"和"不带前导列"两类查询,索引数量更少、写入维护成本更低。