PostgreSQL 架构原理第四期:查询优化器 —— 统计信息与代价模型
Clyde Jin
04-24
DBA
38

PostgreSQL 架构原理第四期:查询优化器 —— 统计信息与代价模型

引言

在前三期的基础上,我们已经了解了 PostgreSQL 的进程模型、存储引擎以及事务并发控制。从用户提交一条 SQL 到真正执行,中间有一个至关重要的环节——查询优化器。它负责在众多可能的执行路径中,找出预计代价最小的那个执行计划。优化器之所以能做出相对准确的决策,核心依赖于两样东西:表的统计信息代价模型

本期我们将深入剖析:

  1. 统计信息的收集与存储:ANALYZE 命令、pg_statistic 系统表、pg_class 中的基本统计
  2. 最常见的数据分布:NULL 分数、唯一值数量、高频值(MCV)、直方图
  3. 代价模型中的启动代价、运行代价、总代价以及各种操作的代价估算公式
  4. 单表扫描(顺序扫描、索引扫描、位图扫描)的代价计算
  5. 连接操作的代价估算(嵌套循环、哈希连接、归并连接)
  6. 统计信息过时或不准时的应对策略

一、统计信息的收集:ANALYZE 命令

统计信息是优化器的“眼睛”。PostgreSQL 通过 ANALYZE 命令(包含在 VACUUM ANALYZE 或自动 autovacuum 中)收集表和索引的统计信息。

1.1 基本统计存储位置

  • pg_class:存储表级和索引级的全局统计,如:
    • reltuples:表的总行数估计值
    • relpages:表的磁盘页数(1 页 = 8KB)
    • relallvisible:可见性映射中标记为全部可见的页数
    • relfrozenxid:表的冻结阈值
  • pg_statistic:存储每个列的具体数据分布统计。为了安全,普通用户只能通过视图 pg_stats 查看,其内容经过脱敏(例如去除隐私数据)。

1.2 ANALYZE 的工作原理

ANALYZE 并不扫描全表(除非表很小),而是采用采样方法:

  • 从每个表或索引中随机抽取约 30000 行(由 default_statistics_target 控制,默认 100,实际采样行数为 300 × 目标值,因为内部乘了 300 的因子)。
  • 对每个目标列进行统计:
    • null_frac:NULL 值比例
    • n_distinct:不同值的数量(如果为负数,表示比例;例如 -0.1 表示大约有 0.1 * 总行数)
    • most_common_valsmost_common_freqs:最常见的值及其出现频率
    • histogram_bounds:等频直方图边界值(当值种类超过一定数量时,不存储 MCV 而使用直方图)
    • correlation:物理行序与逻辑值的相关性(-1 到 1),影响索引扫描代价

采样完成后,将统计信息存入 pg_statistic 并更新 pg_class.reltuplesrelpages

1.3 统计信息收集参数

  • default_statistics_target:默认 100。增大该值会提高采样行数,得到更精确的统计,但 ANALYZE 时间和内存消耗会增加。
  • 可以为特定列设置更高的目标:ALTER TABLE t ALTER COLUMN c SET STATISTICS 500;
  • 自动 ANALYZEautovacuum 触发,当表修改行数达到一定阈值时自动运行。

二、代价模型的基本概念

PostgreSQL 采用基于代价的优化(cost-based optimization),为每个可能的执行计划节点计算一个浮点数值代价。代价的单位并不是真实的时间,而是磁盘顺序页读取的耗时作为基准单位。其他操作(如 CPU 处理、随机页读取)的代价都相对于此进行量化。

2.1 代价的组成部分

每个计划节点都包含三个主要代价字段:

  • startup_cost:获取第一条返回行之前需要付出的代价。例如对索引扫描,需要先读取索引找到第一匹配项。
  • total_cost:获取所有行(假设所有行都被检索)的总代价。
  • run_cost = total_coststartup_cost,通常不直接显示,但内部计算会用到。

优化器最终选择 total_cost 最小的完整计划树,但当使用游标或限制子句时,startup_cost 可能会更重要(例如希望尽快返回第一行)。

2.2 代价参数

代价公式中可配置的成本因子参数(均位于 postgresql.conf):

参数 默认值 含义
seq_page_cost 1.0 顺序页读取代价(基准)
random_page_cost 4.0 随机页读取代价(通常设为 1.5~2.0 对于 SSD)
cpu_tuple_cost 0.01 处理一个元组的 CPU 开销
cpu_index_tuple_cost 0.005 索引扫描时处理一个索引元组的开销
cpu_operator_cost 0.0025 执行一个操作符或函数的开销(如 =+
parallel_setup_cost 1000 并行查询启动子进程的代价
parallel_tuple_cost 0.1 并行查询中传输一个元组的代价

这些参数可以根据硬件特性调整,例如在全闪存存储上降低 random_page_cost 可以提高索引扫描的竞争力。

2.3 代价计算公式举例

处理一个元组的 CPU 代价通常为 cpu_tuple_cost。处理一个 WHERE 子句中的操作符代价为 cpu_operator_cost 乘以操作符个数。读取一个数据页的代价根据顺序或随机读取而不同。


三、单表扫描的代价估算

3.1 顺序扫描(Seq Scan)

顺序扫描整个表,读取所有页面。代价公式:

startup_cost = 0
run_cost = (pages × seq_page_cost) + (tuples × cpu_tuple_cost)
total_cost = run_cost

其中 pages = pg_class.relpagestuples = pg_class.reltuples

如果表有 WHERE 子句,优化器会计算选择率(selectivity),并调整实际返回的元组数。只有满足条件的元组才需要被处理,所以 run_cost 中的 cpu_tuple_cost 需要乘以选择率,但页读取代价仍然需要读取整表(除非顺序扫描能够提前终止,但通常不会)。PostgreSQL 对于顺序扫描不会跳过页面,因此 pages 始终全读。

选择率的估算依赖于统计信息。例如 WHERE col = constant,若 col 有 MCV 统计,则选择率等于该常量值在 MCV 中的频率;若无,则假设均匀分布 1 / n_distinct

3.2 索引扫描(Index Scan)

索引扫描先读取索引获得 TID(页号+行指针),然后根据 TID 直接访问堆表页面。代价估算包括:

  • 索引访问代价:读取索引页(随机 I/O)以及处理索引元组的 CPU 代价。
  • 堆表访问代价:每个 TID 对应一个随机 I/O 读取堆页。

简化公式:

startup_cost = 索引查找的启动代价(如 B-tree 根到叶)
run_cost = (索引页数 × random_page_cost) + 
           (匹配的索引元组数 × cpu_index_tuple_cost) +
           (堆表元组数 × (cpu_tuple_cost + random_page_cost))

需要注意:如果一个索引扫描返回多个元组,且这些元组位于同一个堆页上,PostgreSQL 只计算一次页读取代价,因为可以缓存。实际计算使用更精细的模型,考虑 TID 的聚集度(由 correlation 反映)。

correlation 影响:如果索引键的顺序与物理存储顺序高度相关(correlation 接近 1 或 -1),那么连续访问的 TID 很可能位于相近的堆页,所以随机 I/O 可能退化为顺序 I/O。优化器会使用 random_page_costseq_page_cost 之间的插值来调整。

3.3 位图扫描(Bitmap Scan)

当索引返回大量元组时,位图扫描可以高效减少随机 I/O。步骤如下:

  1. 扫描索引,构造内存中的位图(每个堆页对应一个比特位)。
  2. 根据位图中标记的页面,按物理顺序读取堆页,并检查元组。

代价估算分为两部分:建立位图的代价(索引扫描部分)和根据位图扫描堆表的代价(顺序读取标记的页面)。

总代价 = 索引扫描代价 + 堆表扫描代价(按顺序读页,每页 seq_page_cost)

位图扫描适合当选择率中等的情况,避免每个 TID 都触发随机 I/O。


四、连接操作的代价估算

三种主流连接算法:嵌套循环(Nested Loop)、哈希连接(Hash Join)、归并连接(Merge Join)。

4.1 嵌套循环连接

对于外层表的每一行,扫描内层表所有匹配行。代价公式:

total_cost = outer_startup + outer_run + 
             outer_tuples × (inner_startup + inner_run × selectivity)

其中 selectivity 是内层表在外层每行条件下的匹配率。嵌套循环不需要内层表数据预先加载,适合小型内层表或存在高效索引(内层扫描可用索引加速)。

4.2 哈希连接

先读取内层表,创建哈希表(键为连接列);然后扫描外层表,探测哈希表。通常在内存中进行,超过 work_mem 时会溢写到磁盘。

代价估算:

  • 内层表扫描 + 构建哈希表(写哈希表,CPU 与内存)
  • 外层表扫描 + 探测哈希表
total_cost = 内层表代价 + 外层表代价 + 哈希构建和探测的 CPU 代价(根据行数)

哈希连接适合内层表较大且没有索引,以及等值连接。

4.3 归并连接

要求连接列已排序。如果两个表都按连接列排序,则可以像归并两个有序列表那样连接。代价 = 两个表的排序代价(如果未提前排序) + 归并本身。

归并本身的代价:cpu_tuple_cost 乘以两个表总行数,加上一些比较开销。归并连接适合大数据量且数据已有序的场景。

4.4 连接顺序与搜索空间

当多个表连接时,优化器需要搜索不同的连接顺序(左深树、右深树、浓密树)。from_collapse_limitjoin_collapse_limit 控制是否展开子查询连接。对于超过 12 个表的连接,优化器会采用遗传算法(GEQO),以近似最优。


五、统计信息过时与应对

统计信息并非实时更新:ANALYZE 是采样和异步的。当表频繁更新时,pg_class.reltuples 和列分布可能严重过时,导致优化器选择错误计划。

5.1 识别统计过时

  • 查看执行计划中的 actual rowsestimated rows 差距巨大。
  • 查询 pg_stat_user_tables 中的 n_mod_since_analyze,表示自上次 ANALYZE 后修改的行数。当该值超过一定阈值(如 10%)时,建议重新 ANALYZE

5.2 强制更新统计

  • 手动执行 ANALYZE [table]
  • 调整 autovacuum_analyze_scale_factor(默认 0.1)和 autovacuum_analyze_threshold(默认 50),让自动 ANALYZE 更敏感。

5.3 扩展统计信息

PostgreSQL 10+ 引入了扩展统计CREATE STATISTICS),用于解决列之间相关性强导致联合选择率被低估的问题。

例如 WHERE city = 'Beijing' AND zipcode = '100000',如果 cityzipcode 高度相关,独立假设会低估选择率(乘积太大)。扩展统计可以收集函数依赖多变量 MCV/直方图,帮助优化器更准确估算。


六、实践:如何分析和调优

6.1 使用 EXPLAIN 分析代价

  • EXPLAIN [ANALYZE] 显示计划节点的代价估算与实际执行时间。
  • 关注 cost=xxx..xxx 中的启动代价和总代价。
  • buffers 参数(需要开启 track_io_timing)显示实际读写的块数,可对比估算的页数。

6.2 常见优化器误区

  • 低估索引扫描成本:当 random_page_cost 过高时,索引扫描可能劣于顺序扫描。对于 SSD,建议设置 random_page_cost = 1.0 或 1.1。
  • 高估并发查询:代价模型是单查询视角,不考虑缓存和并发争用。
  • 参数敏感:某些查询在特定参数值下会走不同计划,因为选择率估算受常数值影响。可以使用 pg_hint_planplan_cache_mode 来干预。

6.3 强制计划

虽然 PostgreSQL 没有像 Oracle 的 HINT 语法,但可以通过以下方式影响计划:

  • 设置 enable_* 参数(enable_seqscanenable_indexscan 等)为 off
  • 调整 random_page_cost 等全局成本因子。
  • 使用 SET LOCAL 在会话级别修改参数。
  • 使用扩展 pg_hint_plan 插入伪注释。

总结与预告

本期我们详细解剖了 PostgreSQL 查询优化器的核心:

  • ANALYZE 收集列统计信息(NULL 分数、不同值数量、MCV、直方图、相关性等),存储在 pg_statistic 中。
  • 代价模型以顺序页读取为基准,使用可配置的成本因子计算顺序扫描、索引扫描、位图扫描的代价。
  • 连接操作(嵌套循环、哈希连接、归并连接)各有不同代价公式,优化器会搜索最优连接顺序。
  • 统计信息过时会导致劣质计划,可以通过手动 ANALYZE 或扩展统计来改善。

理解优化器如何工作,有助于解读执行计划、发现选择性估算错误,并合理调整数据库参数。

下一期我们将进入 备份与恢复 的主题,详细讲解 PostgreSQL 的物理备份(PITR)、逻辑备份、流复制与高可用架构,以及如何设计可靠的容灾方案。


思考题

  1. 对于一个大表,WHERE status = 'active',如果 status 列只有两个值(’active’, ‘inactive’),分别占总行数的 10% 和 90%,索引扫描是否总是优于顺序扫描?为什么?
  2. 为什么 PostgreSQL 的 n_distinct 如果是负数表示比例?设计成负数的好处是什么?
  3. 在执行 EXPLAIN ANALYZE 时,你发现实际返回的行数比估计行数多 10 倍,但执行时间仍然很快,这需要处理吗?为什么?

Star
Donate
PostgreSQL 架构原理第三期:事务与并发控制 —— MVCC、快照与锁机制
Previous
PostgreSQL 架构原理第五期:备份与恢复 —— 物理备份、PITR 与复制技术
Next

Leave a comment

Registration is not required

Clyde Jin
303 Articles
0 Comments
0 Like
Recent Posts

HiddenMerit Daily · Issue 60

📊 HiddenMerit Daily · Issue 60 Focus on Database Frontiers, Practical Insights for DBAs July 22, 2026 | 5 Selected Global Breaking News 01|Oracle Releases Largest Quarterly Patch in History: 1,449 Patches Fix 1,235 CVEs, 261 Critical On July 21, Oracle released its July 2026 Critical Patch Update (CPU), setting a record for the largest single patch release in the company’s history. This CPU contains 1,449 security patches fixing 1,235 independent CVEs across 32 Oracle product families, of which 261 patches are rated Critical. Patch Distribution by Product Family: Product Family Patches Remotely Exploitable Without Authentication Oracle E‑Business Suite 410 45 Oracle Fusion Middleware 355 219 Oracle Communications 168 122 Oracle MySQL 54 9 Oracle Database Server 15 6 Context for This Patch: Oracle had already issued an urgent “Prepare Now” warning a week earlier, emphasising that AI is fundamentally lowering the barrier to discovering and exploiting vulnerabilities – frontier AI models can analyse software changes, reverse‑engineer security patches, and develop attack paths at unprecedented speed. Oracle has collaborated with state‑of‑the‑art models from Anthropic and OpenAI to proactively identify and fix potential security vulnerabilities. Oracle strongly recommends that customers immediately test and deploy this update in their monthly patch cycles. […]

HiddenMerit Daily · Issue 59

📊 HiddenMerit Daily · Issue 59 Focus on Database Frontiers, Practical Insights for DBAs July 21, 2026 | 5 Selected Global Breaking News 01|Oracle Issues Urgent Warning: July 21 Release Update to Fix Large Number of High‑Risk Vulnerabilities, Immediate Deployment Recommended On July 13, Oracle issued an urgent security warning, strongly recommending that all customers running supported Oracle Database versions (including Oracle Database 19c and Oracle AI Database 26ai) immediately test and deploy the Release Update (RU) after its release on July 21. Background: New frontier AI models are significantly lowering the barrier to discovering and exploiting software vulnerabilities – these models can identify weaknesses, analyse software changes, reverse‑engineer security patches, and develop potential attack paths at unprecedented speed and scale. AI models are also becoming increasingly adept at combining multiple weaknesses across the application and data stack into complex attacks, even when individual weaknesses do not themselves pose a serious risk. As a result, protecting systems solely at the network or application layer is no longer sufficient; enterprises must protect the entire technology stack. Fixes Included in This RU: Oracle has collaborated with state‑of‑the‑art models from Anthropic and OpenAI to proactively identify and fix potential security vulnerabilities. The upcoming […]

HiddenMerit Daily · Issue 58

📊 HiddenMerit Daily · Issue 58 Focus on Database Frontiers, Practical Insights for DBAs July 20, 2026 | 5 Selected Global Breaking News 01|CAICT: Domestic Databases Enter Core System “Deep Water,” AI‑Native Leads New Industry Landscape On July 9, at the 2026 Trustworthy Database Development Conference, CAICT released the “Database Development Research Report (2026).” The report notes that domestic databases have basically completed peripheral system replacement and have officially entered the critical business system breakthrough phase. Key Data: The global database market reached $131.6 billion in 2025 (approximately RMB 894.09 billion). The Chinese database market reached $9.49 billion in 2025 (approximately RMB 64.48 billion), and is expected to reach RMB 97.974 billion by 2028, with a CAGR of 13.06%. The number of domestic database vendors has shrunk from a peak of 167 to 94, with a clear head‑concentration effect and an intensifying “Matthew effect.” AI‑Native Becomes the Main Theme: The report points out that database technology is accelerating its evolution toward the AI‑native direction, and the global database industry is entering a new phase of landscape restructuring. The role of databases is upgrading from “underlying support systems” to “core engines enabling intelligent decision‑making and business innovation.” AI agents are becoming […]

HiddenMerit Daily · Issue 56

📊 HiddenMerit Daily · Issue 56 Focus on Database Frontiers, Practical Insights for DBAs July 6, 2026 | 5 Selected Global Breaking News 01|Kingware Releases Manufacturing Scenario Database Evolution White Paper: SQL Server Replacement Enters “Deep Water” On July 5, CETC Kingware published a technical article titled “Database Evolution in Manufacturing Scenarios: How Kingware Replaces SQL Server,” pointing out that the data surge on industrial shop floors has exceeded the processing capacity of single‑node systems. Traditional database architectures that rely on vertical scaling are facing unprecedented performance challenges. Over the next 1‑3 years, manufacturing enterprises will no longer face only the “choice of replacement,” but must answer the strategic question: “How do we build an autonomous data foundation in the context of de‑IOE?” Three Paradigm Shifts: Hybrid Workloads Become the Norm: The IT architecture of modern factories is shifting from separated OLTP and OLAP to HTAP mode. The same data system must simultaneously handle tens of thousands of device instruction writes per second and minute‑level production report analysis. Distributed Architecture Becomes a Hard Requirement: When a single table exceeds 100 million rows with daily increments exceeding 1 million rows, the index maintenance cost of traditional single‑node databases rises exponentially. Autonomous […]
生成中...
扫描二维码
扫描二维码