PostgreSQL码农集散地

燃烧吧! PG Bloom 索引扫描狂飙 7 倍性能

本期播客

燃烧吧! PG Bloom 索引扫描狂飙 7 倍性能

数据库查询优化的终极战场,不在 CPU,而在 I/O 的每一微秒。

想象这样一个场景:你的应用需要在一张亿级大表上做任意组合的等值查询——例如根据用户 ID、商品类别、订单状态等多个字段的任意组合筛选数据。你选择了 Bloom 索引,因为它能完美支持这种查询模式。但随着数据量增长,查询响应时间从秒级变成分钟级。你发现瓶颈在于 Bloom 索引扫描的 I/O——它需要读取大量索引页,而每次只能串行等待。

这种煎熬,在 PostgreSQL 19 中彻底终结。Michael Paquier 提交的 4c910f3 补丁,为 Bloom 索引的 bitmap 扫描路径引入了 流式读取(streaming read) ,在 1000 万行数据的测试中,性能提升高达 3 到 7 倍!这不仅是数字的飞跃,更是 I/O 效率革命的又一座里程碑。

第一性原理:为什么 Bloom 索引扫描会慢成瓶颈?

让我们回归 Bloom 索引的本质。Bloom 索引是一种概率型数据结构,用于快速判断一个元素是否属于某个集合。在 PostgreSQL 中,它常用于对多列进行任意组合的等值查询。

Bloom 索引的扫描过程(blgetbitmap())需要读取大量索引页来构建位图。传统的实现方式是同步循环:

for (每个需要扫描的块号) {  
    buffer = ReadBufferExtended(索引, 块号);  // 等待 I/O 完成  
    处理该 buffer 中的数据;  
    ReleaseBuffer(buffer);  
}  

这个模式在现代硬件下面临一个根本性问题:它让 I/O 变得“串行”了。程序发出一个读请求,然后阻塞等待,直到数据从存储介质返回,才继续下一个。这就像在一条单车道高速公路上,即使路边有 10 条车道,你也只能一辆一辆地放行。

第一性原理告诉我们:现代存储设备本质上是高并发的。NVMe SSD 可以同时处理数百个 I/O 请求,由设备内部调度并行处理。串行 I/O 完全无法发挥硬件的真实能力。

PostgreSQL 2026 年度大戏来了, 扫海报中的二维码报名, 选择早鸟或通票(都含午餐和周边礼品), 可私信我要优惠码, 数量有限先到先得!

图片

破局者:Streaming Read 如何实现 3-7 倍飞跃?

这个补丁的核心改动,就是将上述同步循环替换为流式读取:

// 初始化流式读取上下文,告诉内核“我要读这些块”  
stream = read_stream_begin(..., 块号列表);  

while ((buffer = read_stream_next_buffer(stream)) != NULL) {  
    处理该 buffer 中的数据;  
    ReleaseBuffer(buffer);  
}  

背后的魔法:

  1. 异步预取:当程序在处理当前页面时,内核已经在后台并行读取接下来的多个页面。
  2. I/O 合并:流式读取可以将对相邻页面的多次小请求合并为一次大 I/O,减少系统调用开销。
  3. 流水线作业:I/O 等待和 CPU 处理重叠进行,让硬件始终保持忙碌。

权威数据:提交信息中给出了令人震撼的对比结果——在 1000 万行数据的测试中:

  • io_uring 方法:作者报告 3 倍运行时提升,而 Michael Paquier 本人测得接近 7 倍!
  • worker 方法(3 个 worker) :作者报告的运行时提升比 Michael 的测试更好,且 IO 统计数据的减少在所有测试案例中都非常显著。

这意味着:你的 Bloom 索引越大,这个优化的收益就越显著。

量化分析:7 倍提升意味着什么?

假设一个典型场景:一张 5 亿行的用户行为表,上面有一个 Bloom 索引用于多字段组合查询。每次查询需要扫描 10% 的索引页(约 5000 页)。

  • 旧方式:5000 次同步 I/O,假设每次 0.1 毫秒(高速 SSD),总 I/O 时间 500 毫秒,加上 CPU 处理,总响应时间约 800 毫秒。
  • 优化后:通过流式读取,I/O 时间降低到 70 毫秒(7 倍提升),总响应时间约 150 毫秒。

对于高频查询:假设每秒 100 次这样的查询,旧方式需要 80 个 CPU 核心才能处理,而优化后只需 15 个核心。 硬件成本降低 80% 。

权威案例:流式读取正在成为新范式

这个优化不是孤例。Xuneng Zhou(该补丁的作者)在邮件列表中表示,他还在 pgstattuple 等其他模块中应用了相同的技术。此前,Bloom 索引的 VACUUM 操作也通过流式读取获得了 30% 的性能提升(commit d841ca2)。GIN 索引的 VACUUM 更是获得了 5 倍的提升(commit 6c228755)。

这表明:流式读取正在成为 PostgreSQL I/O 子系统的新标准,从核心索引到 contrib 模块,从 VACUUM 到查询扫描,全面受益。

第一性原理的边界:什么时候这个优化会失效?

任何优化都有前提。流式读取的核心前提是:你能提前知道需要读取哪些块,并且这些块的顺序是确定的。当这个前提崩塌时,效果可能打折扣。

崩塌场景 1:I/O 延迟极低(如内存表)

如果索引已经完全缓存在内存中,ReadBufferExtended() 几乎不产生 I/O 等待,那么流式读取的预取优势就无法体现。但此时,优化也不会造成伤害——它只是多了几次函数调用而已。

崩塌场景 2:存储设备本身是单线程的(老式 HDD)

在单块机械硬盘上,并发 I/O 的收益有限,因为磁头物理上只能服务一个请求。但即便如此,流式读取仍能通过减少系统调用次数和 I/O 合并带来小幅提升。7 倍的收益主要是在现代 SSD 和 io_uring 环境下测得的,这代表了未来主流存储的趋势。

崩塌场景 3:查询选择性极高,只需读取少量页

如果查询只需要读取 1-2 个索引页,流式读取的预取优势无法发挥。但此时,优化也不会造成额外负担。

崩塌场景 4:你没有使用 Bloom 索引

这个优化专为 Bloom 索引设计。如果你的业务没有使用 Bloom 索引(比如只用 B-tree 或 GIN),这个优化不直接受益。但好消息是,流式读取的哲学正在渗透到更多模块,未来的扫描优化可能会惠及所有索引类型。

DBA 的行动指南

1. 检查你是否在用 Bloom 索引

SELECT schemaname, tablename, indexname, indexdef  
FROM pg_indexes  
WHERE indexdef LIKE'%bloom%';  

如果你看到输出,说明你正在使用 Bloom 索引。升级到 PostgreSQL 19 后,查询将自动受益。

2. 升级并验证性能

在测试环境,对一张包含大 Bloom 索引的表执行典型查询,并对比 PG18 和 PG19 的执行时间:

\timing on  
SELECT * FROM your_table WHERE col1 = 'value1'AND col2 = 'value2';  

记录时间。如果可能,用 EXPLAIN (ANALYZE, BUFFERS) 观察 I/O 统计的变化。

3. 调整 I/O 相关参数

为了最大化流式读取的效果,确保以下参数合理设置:

-- 启用异步 I/O(如果硬件支持)  
ALTERSYSTEMSET io_method = 'io_uring';  -- 或 'worker'  
-- 设置合适的 worker 数量(如果使用 worker 方法)  
ALTERSYSTEMSET io_workers = 16;  
SELECT pg_reload_conf();  

4. 监控 I/O 效率

使用 pg_statio_user_indexes 观察优化前后的 I/O 变化:

SELECT indexname,   
       blks_read,   
       blks_hit,   
round(blks_hit * 100.0 / NULLIF(blks_read + blks_hit, 0), 2) AS hit_ratio  
FROM pg_statio_user_indexes   
WHERE indexname = 'your_bloom_index';  

流式读取应能提高缓存命中率,减少实际物理读。

未来展望:流式读取将无处不在

这个补丁再次印证了 PostgreSQL 社区对 I/O 子系统的持续投入。未来,我们很可能看到:

  • 所有索引类型的扫描都采用流式读取
  • 顺序扫描也能受益于流式预取
  • 更智能的预取策略,结合统计信息和查询模式

结语

PostgreSQL 19 对 Bloom 索引扫描的优化,看似是一个索引类型的专属改进,实则是将现代 I/O 理念深植于数据库内核的又一力证。3 到 7 倍的性能提升,来源于对硬件特性的深刻理解和巧妙的工程实现。

对于 DBA 而言,这意味着更快的查询响应、更低的硬件成本、更平稳的业务运行。升级到 PostgreSQL 19,不仅是为了新功能,更是为了让你的硬件投资发挥出应有的效能。

从今天起,当你使用 Bloom 索引进行组合查询时,可以放心:PostgreSQL 19 已经为你铺就了 I/O 的高速公路。