DeepSeek总结的 pg_regresql插件:真正可移植的 PostgreSQL 统计信息

原文地址:https://postgr.es/p/7vW

pg_regresql:真正可移植的 PostgreSQL 统计信息

2026-03-21 · 5分钟阅读 · Radim Marek

目录

上一篇文章展示了 PostgreSQL 18 使得优化器统计信息变得可移植,但仍留下了一个缺口:

尝试注入 relpages 是没用的,因为规划器会检查实际文件大小并按比例进行缩放。

规划器不信任 pg_class.relpages。它会调用 smgrnblocks() 从磁盘读取实际的 8KB 页面数。你的表在磁盘上有 74 个页面,但 pg_class.relpages 显示为 123,513?规划器会使用这个比例来缩放 reltuples,使其与实际文件大小匹配。选择性比例保持正确,计划形态大多得以保留,但绝对成本估计值会偏离。

对于调试单个查询,这通常没问题。但对于跨运行比较 EXPLAIN 成本的自动化回归测试,这就出问题了。当基线是从虚假缩放的数字计算出来时,2 倍的成本阈值就有了不同的含义。

作为我在 RegreSQL 上工作的一部分,我很高兴地宣布推出 pg_regresql 扩展,它通过直接钩入规划器来修复这个问题。


为什么规划器忽略 relpages

当 PostgreSQL 规划器在 plancat.c 中调用 get_relation_info() 时,它会委托给 estimate_rel_size(),最终到达 tableam.c 中的 table_block_relation_estimate_size()。在那里,实际的页面数来自存储管理器:

c 复制代码
curpages = RelationGetNumberOfBlocks(rel);

然后,该函数从 pg_class 计算元组密度(reltuples / relpages),并将其乘以 curpages 来估计元组数。所以 pg_class.reltuples 并没有被忽略,而是被缩放以匹配实际文件大小。这种推理在正常操作中是合理的:目录可能过时,但文件系统始终是最新的。

索引也是如此。规划器也会从磁盘读取它们的实际大小。


pg_regresql 的作用

该扩展钩入 get_relation_info_hook,这是一个在 PostgreSQL 读取物理文件统计信息后运行的规划器回调。该钩子用存储在 pg_class 中的值替换基于文件大小的数字:

  • rel->pagespg_class.relpages
  • rel->tuplespg_class.reltuples
  • rel->allvisfracpg_class.relallvisible / pg_class.relpages

它对于 rel->indexlist 中的每个索引也执行相同的操作。每个索引的页数和元组数都会从其自身的 pg_class 条目中被覆盖。

保护条件很简单:如果 relpages == 0(表为空或从未分析过)或 reltuples == -1(从未分析过),则跳过覆盖。该钩子仅对已执行 ANALYZE 或注入了统计信息的表激活。


安装

使用 PGXS 从源码构建:

bash 复制代码
cd pg_ext
make
make install

它不需要 GUC、后台工作进程或共享内存。


使用方法

在你的会话中加载扩展:

sql 复制代码
LOAD 'pg_regresql';

就这样。此会话中的每个 EXPLAIN 现在都将使用目录统计信息而不是文件大小。无需调用任何函数,也无需配置任何表。

你也可以通过将其添加到 postgresql.conf 中的 session_preload_libraries 或通过 ALTER DATABASE 来按数据库加载:

sql 复制代码
ALTER DATABASE test_db SET session_preload_libraries = 'pg_regresql';

它带来的改变

使用上一篇文章中相同的 test_orders 示例:10,000 条实际行,注入了声称有 5000 万行分布在 123,513 个页面上的生产统计信息。

不使用 pg_regresql:

sql 复制代码
EXPLAIN SELECT * FROM test_orders WHERE created_at > '2024-06-01';
复制代码
                                             QUERY PLAN
----------------------------------------------------------------------------------------------------
 Index Scan using test_orders_created_at_idx on test_orders  (cost=0.29..153.21 rows=6340 width=26)
   Index Cond: (created_at > '2024-06-01'::date)

计划形态是正确的(得益于直方图,使用了索引扫描),但行估计是 6,340。对于一个 5000 万行的表,且过滤条件覆盖了大约 10% 的直方图范围,预期的估计值应该在数百万。规划器看到磁盘上有 74 个实际页面,将 reltuples 缩放至约 30,000,然后应用选择性。比例保留了,但绝对值是错误的。

使用 pg_regresql:

sql 复制代码
LOAD 'pg_regresql';
EXPLAIN SELECT * FROM test_orders WHERE created_at > '2024-06-01';
复制代码
                                                QUERY PLAN
-----------------------------------------------------------------------------------------------------------
 Index Scan using test_orders_created_at_idx on test_orders  (cost=0.29..153212.27 rows=10791836 width=27)
   Index Cond: (created_at > '2024-06-01'::date)
(2 rows)

成本数字现在反映了完整的 5000 万行。


这为什么重要

  • 基于成本的回归测试 。如果你在比较不同模式版本之间的 EXPLAIN 成本(这正是 RegreSQL 所做的),你需要绝对数字稳定且真实。由于存在缩放行为,你的基线成本与你的测试数据库大小成比例,而不是生产环境。生产中使成本翻倍的迁移,在 CI 中可能只显示 1.3 倍的增长,因为缩放后的数字压缩了变化范围。

  • 在笔记本电脑上重现生产计划。有时计划形态本身会根据绝对数字而变化。当规划器看到 5000 万行对比 30,000 行时,涉及多个连接的查询可能会得到不同的连接顺序,因为哈希连接和嵌套循环之间的成本交叉点取决于绝对行数,而不仅仅是比例。

  • 仅索引扫描allvisfrac(全可见页面的比例)对于仅索引扫描的成本计算很重要。没有这个钩子时,allvisfrac 是通过真实的 relallvisible 目录值除以实际页面数来计算的。对于注入的统计信息,relallvisible 可能是 120,000,但实际页面数是 74,所以该分数被钳位到 1.0,规划器会低估仅索引扫描的成本。该钩子通过使用注入的 relpages 作为分母来修复此问题。


它不能做什么

列级统计信息和 ANALYZE 行为保持不变。该扩展仅影响规划器读取表和索引大小。有一点需要注意:EXPLAIN ANALYZE 仍然会显示来自实际(小规模)数据的实际行数。该扩展改变的是规划器的成本估计,而不是查询执行。


完整工作流程

结合 PostgreSQL 18 的可移植统计信息和 pg_regresql,完整的工作流程如下所示:

bash 复制代码
# 1. 从生产环境转储模式和统计信息
pg_dump --schema-only -d production_db > schema.sql
pg_dump --statistics-only -d production_db > stats.sql

# 2. 创建测试数据库
createdb test_db
psql -d test_db -f schema.sql

# 3. 加载最小的测试数据(可选)
psql -d test_db -f fixtures.sql

# 4. 注入生产统计信息
psql -d test_db -f stats.sql

# 5. 安装 pg_regresql 并防止统计信息被覆盖
psql -d test_db <<SQL
ALTER DATABASE test_db SET session_preload_libraries = 'pg_regresql';
ALTER TABLE orders SET (autovacuum_enabled = false);
-- 对其他表重复此操作
SQL

# 6. 重新连接并验证(计划现在与生产环境匹配)
psql -d test_db -c "EXPLAIN SELECT * FROM orders WHERE status = 'pending'"

兼容性

该扩展适用于 PostgreSQL 13 到 18。可移植统计信息函数(pg_restore_relation_statspg_restore_attribute_stats)需要 PostgreSQL 18,但 pg_regresql 适用于任何写入 pg_class 的方法,包括在旧版本上直接更新目录。

PostgreSQL 19 将需要一个小的更新:pg_regresql 使用的 get_relation_info_hook 已被 build_simple_rel_hook 替换。新的钩子运行时间稍晚,参数也不同,但覆盖逻辑保持不变。


请勿在生产环境中使用

该扩展使规划器忽略现实。这正是你在使用注入统计信息进行测试时想要的。在生产环境中,你希望规划器看到实际的文件大小,以便它能够适应数据增长、膨胀和 vacuum 活动。请将 pg_regresql 留在你的开发/测试和 CI 数据库中。

相关推荐
oradh2 小时前
Oracle 11.2.0.1版本升级至11.2.0.4_单机环境
数据库·oracle·oracle11g·oracle升级
l1t2 小时前
用docker安装测试crate数据库
数据库·docker·容器·cratedb
anzhxu2 小时前
QT数据库(三):QSqlQuery使用
数据库·qt·oracle
身如柳絮随风扬2 小时前
MySQL核心知识
数据库·mysql
德彪稳坐倒骑驴2 小时前
Oracle 11g安装
数据库·oracle
韩立学长2 小时前
Springboot校园跑腿业务系统0b7amk02(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
数据库·spring boot·后端
阿贵---2 小时前
使用XGBoost赢得Kaggle比赛
jvm·数据库·python
想七想八不如114082 小时前
数据库--样题复习
数据库·sql·oracle
551只玄猫2 小时前
【数据库原理 实验报告1】创建和管理数据库
数据库·sql·学习·mysql·课程设计·实验报告·数据库原理