由 John Doe 十二月 5, 2025
PostgreSQL 18 支持跳跃扫描式查找,使多列 B 树索引在更多情况下可用。该优化允许 PostgreSQL 即便在未对索引前置列进行过滤的情况下,也能使用多列 B 树索引,从而为实际业务中的分析类查询和报表查询带来显著性能提升,而且完全无需添加新索引。

理解“最左索引”问题
B 树跳跃扫描特性是 PostgreSQL 18 中最受期待的一个查询优化改进。它解决了一个困扰数据库管理员和开发者多年的长期限制,也体现了 PostgreSQL 社区对提升数据库性能与效率的持续投入。
在深入了解跳跃扫描特性之前,我们先来明确它所解决的“最左索引”限制问题。在 PostgreSQL 早期版本中,多列 B 树索引的高效性主要体现在查询包含“索引前置列过滤条件”的场景中。多列 B 树索引的结构逻辑是:先按第一列排序,再在第一列的每个值范围内按第二列排序,以此类推。
以一个基于(status, customer_id, order_date)的多列 B 树索引为例,索引叶子节点的存储顺序遵循字典序,如下所示:
('active', 101, '2024-01-01')
('active', 101, '2024-01-15')
('active', 102, '2024-01-03')
('pending', 101, '2024-01-10')
('pending', 103, '2024-01-20')
('shipped', 101, '2024-01-05')
...
对于包含status = 'active' AND customer_id = 101这类过滤条件的查询,数据库会执行一次连续的范围扫描,效率极高。但如果查询仅对customer_id = 101进行过滤(未包含前置列status),那么符合条件的索引条目会分散在所有status分组中。在这种情况下,PostgreSQL 通常会选择执行全表顺序扫描,或使用其他可用索引,导致你精心设计的多列索引完全被闲置。
这一限制迫使 DBA 创建多个“列顺序不同”的索引,以覆盖不同的查询模式,这不仅会增加存储开销、降低写入性能,还会让索引管理变得更加复杂。
用跳跃扫描来解决问题
PostgreSQL 18 为 B 树索引引入了跳跃扫描特性,即使索引前置列没有等值过滤条件,查询规划器也能使用多列索引。这彻底改变了“因查询未过滤索引首列,导致优质索引被闲置”的尴尬局面。
跳跃扫描优化的核心逻辑是:允许 PostgreSQL 智能地跳过索引中的部分数据,找到目标数据。当你查询索引的非前置列(未指定前置列条件)时,PostgreSQL 现在可以:
- 识别出被省略的前置列的所有不同值(即“去重值”);
- 自动改写查询,为每个前置列值添加对应的过滤条件;
- 利用现有索引机制,优化对多个前置列值的查找过程,本质上是跳过索引中不符合查询条件的页面。
这一特性对分析型和报表型工作负载尤为重要:在这类场景中,你经常需要查询索引列的不同组合,且不一定每次都指定前置列条件。
跳跃扫描的内部原理
我们通过一个实际示例来理解其工作机制。假设我们有一个orders表,并创建了如下索引:
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
status VARCHAR(20),
customer_id INTEGER,
order_date DATE,
amount DECIMAL(10,2)
);
CREATE INDEX idx_orders ON orders(status, customer_id, order_date);
在 PostgreSQL 18 之前,若执行如下查询:
SELECT * FROM orders
WHERE customer_id = 123
AND order_date > '2025-01-01';
由于查询未过滤前置列status,上述索引基本无法发挥作用,PostgreSQL 很可能会执行全表顺序扫描。
而在 PostgreSQL 18 中,借助跳跃扫描特性,查询规划器能高效利用该索引,其本质是自动改写查询逻辑:首先识别出status的所有不同值(例如pending、active、shipped),然后针对每个status值,结合customer_id和order_date条件执行定向索引扫描。最终改写后的查询逻辑等效于:
SELECT * FROM orders WHERE status = 'pending' AND customer_id = 123 AND order_date > '2025-01-01'
UNION ALL
SELECT * FROM orders WHERE status = 'active' AND customer_id = 123 AND order_date > '2025-01-01'
UNION ALL
SELECT * FROM orders WHERE status = 'shipped' AND customer_id = 123 AND order_date > '2025-01-01';
这里的关键洞察是:如果被省略的前置列“基数较低”(即不同值的数量较少),那么对每个不同值执行扫描的开销,与全表顺序扫描相比会非常小。PostgreSQL 的查询规划器会自动判断:跳跃扫描是否比顺序扫描或其他方案具有更好的性能。
跳跃扫描的适用场景
跳跃扫描在以下场景中能发挥最大价值:
- 前置列基数较低:当被省略的前置列基数较低时,优化效果最为显著。例如,若
status列仅有 3-5 个不同值,跳跃扫描的性能会非常出色;但如果该列有数千个不同值,其优势会大幅减弱。 - 非前置列有等值过滤条件:当前版本的跳跃扫描实现,主要针对非前置列包含等值过滤条件的场景进行了优化。规划器会优先识别这类查询模式并应用跳跃扫描。
- 分析型与报表型工作负载:在需要灵活查询“列组合”的分析类场景中,跳跃扫描的价值尤为突出,例如商业智能(BI)工具查询、临时报表生成等场景。
- 避免索引泛滥:无需再为覆盖不同查询模式而创建“列顺序不同”的多个索引,现在只需设计一个优质的多列索引,跳跃扫描就能使其高效适配多种查询需求。
重要限制与注意事项
尽管跳跃扫描功能强大,但仍需了解其当前的局限性:
- 仅支持 B 树索引:目前跳跃扫描仅适用于 B 树索引(PostgreSQL 中最常用的索引类型),暂不支持其他索引类型(如 GiST、GIN 等)。
- 性能依赖基数:随着被省略前置列的不同值数量(基数)增加,跳跃扫描的性能优势会显著下降。若前置列基数较高,仍需为非前置列创建专用索引。
- 需要非前置列有等值条件:该特性要求非前置列至少包含一个等值过滤条件,对于“非前置列仅为范围条件”或“复杂过滤条件”的场景,无法发挥作用。
- 大结果集场景不适用:若查询需返回大量结果(大结果集),传统的位图扫描或全表顺序扫描,可能仍是更优选择。
实际示例与性能分析
通过一个更详细的示例,我们来直观感受跳跃扫描的性能提升。首先创建一个sales表,并插入具有真实数据分布的样本数据:
-- Create the sales table
CREATE TABLE sales (
sale_id SERIAL PRIMARY KEY,
region VARCHAR(20),
product_category VARCHAR(50),
sale_date DATE,
amount DECIMAL(10,2)
);
-- Create multicolumn index
CREATE INDEX idx_sales_region_category_date
ON sales (region, product_category, sale_date);
-- Insert sample data
INSERT INTO sales (region, product_category, sale_date, amount)
SELECT
CASE (random() * 4)::int
WHEN 0 THEN 'North'
WHEN 1 THEN 'South'
WHEN 2 THEN 'East'
ELSE 'West'
END,
'Category_' || (random() * 20)::int,
'2024-01-01'::date + (random() * 365)::int,
(random() * 1000)::numeric(10,2)
FROM generate_series(1, 1000000);
ANALYZE sales;
现在,让我们使用 PostgreSQL 17 按product_category查询,而不指定region:
EXPLAIN ANALYZE
SELECT * FROM sales
WHERE product_category = 'Category_5'
AND sale_date > '2024-06-01';
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
Gather (cost=1000.00..18244.90 rows=29289 width=30) (actual time=0.382..47.816 rows=29343 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Parallel Seq Scan on sales (cost=0.00..14316.00 rows=12204 width=30) (actual time=0.015..29.794 rows=9781 loops=3)
Filter: ((sale_date > '2024-06-01'::date) AND ((product_category)::text = 'Category_5'::text))
Rows Removed by Filter: 323552
Planning Time: 0.216 ms
Execution Time: 48.527 ms
(8 rows)
如您所见,在 PostgreSQL 17 中,由于未指定前置的 region 列,这个查询使用了顺序扫描。在 PostgreSQL 18 中,规划器可以使用跳跃扫描,通过扫描四个 region 值并执行定向查找来高效利用索引。
在 PostgreSQL 18 中执行相同查询:
EXPLAIN ANALYZE
SELECT *FROM sales
WHERE product_category='Category_5'
AND sale_date>'2024-06-01';
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on sales (cost=457.63..8955.11 rows=28832 width=30) (actual time=2.671..11.931 rows=29202 loops=1)
Recheck Cond: (((product_category)::text = 'Category_5'::text) AND (sale_date > '2024-06-01'::date))
Heap Blocks: exact=7850
Buffers: shared hit=7917
-> Bitmap Index Scan on idx_sales_region_category_date (cost=0.00..450.43 rows=28832 width=0) (actual time=1.916..1.917 rows=29202 loops=1)
Index Cond: (((product_category)::text = 'Category_5'::text) AND (sale_date > '2024-06-01'::date))
Index Searches: 9
Buffers: shared hit=67
Planning:
Buffers: shared hit=45 read=1
Planning Time: 0.189 ms
Execution Time: 12.801 ms
(12 rows)
PostgreSQL 18 的查询计划显示,跳跃扫描已生效:通过“位图索引扫描”(基于原有多列索引),执行时间从 48.5 毫秒降至约 12.8 毫秒,同时缓冲区读取量大幅减少,性能提升显著。
配置与调优
PostgreSQL 18 将跳跃扫描作为查询规划器的一个内置能力,规划器会基于“成本估算”自动决定是否使用跳跃扫描。
与其他规划器优化类似,PostgreSQL 允许通过配置参数启用或禁用跳跃扫描,但在正常使用中,建议让规划器根据统计信息和成本估算自主决策,无需手动干预。
未来展望
跳跃扫描特性是 PostgreSQL 在查询优化和索引利用率提升方面迈出的重要一步,体现了社区在“持续提升性能”的同时,始终保持数据库可靠性与稳健性的承诺。
这一功能解决了开发者和 DBA 多年来不得不通过“变通方案”应对的痛点:通过更灵活地利用多列索引,跳跃扫描简化了数据库设计、降低了存储开销,并在多种场景中提升了查询性能。
随着 PostgreSQL 的持续演进,我们有望看到跳跃扫描及其他查询优化功能的进一步增强。PostgreSQL 18 奠定的技术基础,未来可能会扩展到更复杂的查询模式和更多索引类型(如 GiST、GIN 等)。
总结
PostgreSQL 18 的 B 树跳跃扫描功能,填补了多列索引利用率的长期空白,如今,即便省略索引最左列,多列 B 树索引也不再“完全无用”。对于“前置列基数低+非前置列有等值条件”的场景,无需创建额外索引,就能充分发挥多列索引的性能优势。
PostgreSQL 社区通过每一个版本,持续证明其致力于让数据库更高效、更具扩展性、更适配企业级需求的决心。跳跃扫描只是 PostgreSQL 18 众多改进中的一项,这些功能共同增强了数据库对现代应用工作负载的适配能力。
在 18 版本之后,PostgreSQL 将继续发展与完善:更多查询优化、更好的分析型工作负载支持、对性能与扩展性的持续聚焦,都将是未来的重点方向。PostgreSQL 的前景光明,而跳跃扫描这类特性也证明,社区始终在倾听用户需求,并切实解决实际业务中的挑战。
对于使用 PostgreSQL 的 DBA 和开发者而言,跳跃扫描无疑是一个备受欢迎的功能,它简化了索引管理,提升了查询性能。在规划升级到 PostgreSQL 18 时,建议你回顾现有的多列索引,并识别出可通过跳跃扫描获益的查询,或许能找到“合并索引、提升整体数据库性能”的机会。
参考
提交日志:https://git.postgresql.org/pg/commitdiff/92fe23d93aa3bbbc40fca669cabc4a4d7975e327