SQL 与查询优化(PostgreSQL 篇)· 第三期
Clyde Jin
04-24
DBA
48

SQL 与查询优化(PostgreSQL 篇)· 第三期

连接优化与统计信息深度调优

经过前两期,你已经能读懂执行计划、用好窗口函数与 CTE。本期进入多表连接优化的核心地带,并深挖优化器的“大脑”——统计信息。 我们将剖析三种连接算法背后的代价模型,学会用扩展统计信息解决多列关联的基数误判,最终通过真实案例把执行时间从分钟级降至毫秒级。

一、连接算法深度解析 – 优化器的三种武器

当查询涉及多个表时,PostgreSQL 优化器需要在 Nested LoopHash JoinMerge Join 中做出选择,并决定连接顺序。这个决策的基础是 基数估计(每步返回的行数)与 代价计算

1.1 Nested Loop – 索引依赖之王

原理:对外层结果集的每一行,遍历内层表查找匹配行。 代价cost = outer_rows * inner_scan_cost

  • 如果内层走索引(Index Scan),inner_scan_cost ≈ O(log N);如果内层全表扫描,代价爆炸。 适用场景
  • 外层表很小(例如只返回几十行)。
  • 内层表有高效索引(通常是连接键)。
  • 支持非等值连接(如 a.id > b.id),这是 Hash Join 做不到的。

执行计划识别

Nested Loop  (cost=0.29..2150.30 rows=10)
  ->  Index Scan using idx_users_id on users  (rows=1)
  ->  Index Scan using idx_orders_user_id on orders  (rows=10)

1.2 Hash Join – 中等数据集的王者

原理:先构建一个表的哈希表(通常在内存中),然后扫描另一个表探测。 代价cost = build_cost + probe_cost ≈ O(outer + inner)(无索引也可)。 适用场景

  • 两表较大,但连接键无索引或索引选择性不佳。
  • 等值连接(=IN)。
  • 内存足够容纳哈希表(work_mem 参数控制,不足则会 spill 到磁盘,严重降低性能)。

执行计划识别

Hash Join  (cost=5000..12000 rows=50000)
  Hash Cond: (o.user_id = u.id)
  ->  Seq Scan on orders o  (rows=1000000)
  ->  Hash  (cost=3000..3000 rows=100000)
        ->  Seq Scan on users u

调优点

  • 增大 work_mem 避免哈希表落盘(监控 temp_files)。
  • 调整连接顺序:让行数较少的表作为 build 表(哈希表源)。

1.3 Merge Join – 排序的交换

原理:两表按连接键预先排序,然后像合并有序数组一样归并。 代价cost = sort_cost(outer) + sort_cost(inner) + merge_cost适用场景

  • 连接键本身有序(例如已有索引,或子查询带 ORDER BY)。
  • 连接条件为不等式(<, > 等)。
  • 数据量非常大且 Hash Join 因内存不足会频繁落盘时。

执行计划识别

Merge Join  (cost=15000..18000 rows=50000)
  Merge Cond: (o.user_id = u.id)
  ->  Index Scan using idx_orders_user_id on orders  (rows=1000000)
  ->  Sort  (cost=10000..10200 rows=80000)
        Sort Key: u.id
        ->  Seq Scan on users u

注意:如果两表都需要显式排序,额外开销可能超过 Hash Join。

1.4 连接顺序 – 基数的艺术

多表连接时(如 A JOIN B JOIN C),优化器会尝试不同的连接顺序。基数估计的准确性直接影响顺序选择。例如:

SELECT * FROM A JOIN B ON A.x = B.x JOIN C ON B.y = C.y;
  • 若优化器认为 A JOIN B 只返回 10 行,会倾向于用那 10 行作为外层去连接 C(Nested Loop)。
  • 若实际返回 10 万行,则 Hash Join 可能更优,但执行计划已固定,性能崩溃。

强制顺序的方法

  • SET join_collapse_limit = 1(不推荐,杀死优化器灵活性)。
  • 使用 CTE 控制物化顺序(WITH t AS MATERIALIZED (SELECT ...))或明确 OFFSET 0 技巧。
  • 更推荐:修正统计信息,让优化器自动选择正确顺序。

二、统计信息的深层秘密

优化器的一切估计都源自 pg_statistic(视图 pg_stats)。不准确的统计信息是慢查询的首要元凶。

2.1 核心统计字段解读

pg_stats 为例(针对表 ordersstatus 列):

SELECT * FROM pg_stats WHERE tablename = 'orders' AND attname = 'status';
字段 含义 对优化器的影响
n_distinct 不同值的数量。负数为比例(如 -0.1 表示约 10% 的行是唯一值) 决定使用等值过滤时的选择性
most_common_vals (MCV) 出现频率最高的值列表 若查询值在 MCV 中,直接读频率,无需扫描直方图
most_common_freqs 对应 MCV 的频率 同上
histogram_bounds 将数据分布均匀分桶的边界值(排除 MCV 后) 用于估算范围条件(><BETWEEN)的返回行数
correlation 物理行顺序与逻辑值的相关系数(-1 到 1) 接近 1 或 -1 时,索引扫描可大幅减少随机 I/O;接近 0 时索引扫描可能不如全表扫描
null_frac 空值比例 影响 IS NULL 条件的估算

重要histogram_bounds 默认 100 个桶,可通过 ALTER TABLE SET STATISTICS 提高精度。

2.2 直方图与 MCV 的实战应用

假设查询:SELECT * FROM orders WHERE amount BETWEEN 100 AND 200

优化器的估算过程:

  1. 先看 amount 列的 MCV,若 100~200 区间内包含某些高频值,直接用对应频率。
  2. 排除 MCV 后,剩下行的数据分布由 histogram_bounds 描述(例如边界为 [1, 50, 90, 150, 210, 300, …])。
  3. 计算落在 [100,200] 的桶的数量,估算行数 = 总行数 × 比例。

若数据分布不均匀(例如 90% 的行 amount 在 100~200 内,但只有 10% 的行在其他范围),默认直方图可能低估,导致优化器错误选择全表扫描而不是索引。解决办法:提高统计精度。

2.3 提高统计信息质量

命令

ALTER TABLE orders ALTER COLUMN amount SET STATISTICS 1000;  -- 默认 100,最大 10000
ANALYZE orders;
  • 更大的统计目标会增加 ANALYZE 时间和存储,但对关键列值得投入。

查看当前统计目标

SELECT attname, attstattarget FROM pg_attribute WHERE attrelid = 'orders'::regclass;

2.4 扩展统计信息 – 解决多列相关性

这是 PostgreSQL 10+ 引入的杀手级功能。当查询条件涉及 多个列 并且它们之间存在相关性时,普通单列统计会导致严重误判。

经典问题:表 weathercitymonth 列。查询:

SELECT * FROM weather WHERE city = 'Beijing' AND month = 'July';
  • 单列统计:知道 Beijing 占 10% 的行,July 占 8% 的行,假设独立 → 0.8% 行。
  • 实际:Beijing 在 July 可能占 Beijing 所有数据的 80%,总比例可能 8%。优化器低估,可能选择 Index Scan 当实际上更需 Seq Scan,或反之导致错误连接顺序。

解决方案 – 创建扩展统计信息

CREATE STATISTICS stts_city_month ON city, month FROM weather;
ANALYZE weather;
  • 这会在 pg_stats_ext 中生成 多列 MCV(最常用组合)和 函数依赖
  • 优化器使用扩展统计后,city = 'Beijing' AND month = 'July' 的行数估算准确。

查看扩展统计

SELECT * FROM pg_stats_ext WHERE tablename = 'weather';

适用场景

  • 多列 GROUP BY 基数估算。
  • 多列条件连接(例如 A.c1 = B.c1 AND A.c2 = B.c2)的基数估算。
  • 带有多个过滤条件的 WHERE 子句。

注意:扩展统计不会自动创建,需要 DBA 主动分析查询模式后添加。


三、实战案例:连接顺序错误 + 统计信息陈旧

场景描述

电商系统,三张表:

-- 订单表 orders:1 亿行,order_date 有索引,status 只有 'paid'/'pending'/'canceled'
-- 用户表 users:1000 万行,id 主键
-- 用户标签表 user_tags:5000 万行,user_id, tag, created_at

慢查询(统计过去 30 天带 ‘VIP’ 标签的用户的已支付订单总额):

SELECT u.id, u.name, sum(o.amount) AS total
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN user_tags t ON u.id = t.user_id
WHERE o.order_date >= now() - interval '30 days'
  AND o.status = 'paid'
  AND t.tag = 'VIP'
GROUP BY u.id, u.name;

原始执行时间:150 秒

执行计划分析(简化)

HashAggregate  (actual time=150000ms)
  ->  Nested Loop  (actual rows=500 loops=1)
        ->  Nested Loop  (actual rows=50000)
              ->  Seq Scan on user_tags t  (rows=5000000)    Filter: tag = 'VIP'
              ->  Index Scan on users u (rows=1)
        ->  Index Scan on orders o  (rows=10) 
              Index Cond: (user_id = u.id)
              Filter: (order_date >= ...) AND (status = 'paid')

问题诊断

  1. 驱动表错误:优化器以 user_tags 作为最外层表,全表扫描所有 tag=’VIP’ 的行(500 万行)。实际上 tag=’VIP’ 并不多(50 万行),但因统计信息未及时更新,n_distinct 估算错误。
  2. 连接顺序:5 万行与 orders 连接时,orders 使用 Index Scan,但因每个用户在过去 30 天有多个订单,导致回表次数过多。
  3. 统计问题:user_tags 表的 tagmost_common_freqs 未包含 ‘VIP’(因为最近业务调整,VIP 比例从 5% 升至 20%,但未重新分析)。

优化步骤

Step 1:更新统计信息

ANALYZE user_tags;

再次执行,优化器发现 tag=’VIP’ 占 20%,选择其他连接顺序。

新计划(简化):

Hash Join  (actual time=8000ms)
  Hash Cond: (o.user_id = u.id)
  ->  Index Scan using idx_orders_date_status on orders (rows=2000000)
        Index Cond: (order_date >= ...)
        Filter: (status = 'paid')
  ->  Hash
        ->  Hash Join (actual rows=2000000)
              Hash Cond: (t.user_id = u.id)
              ->  Index Scan using idx_user_tags_tag on user_tags (rows=500000)
                    Index Cond: (tag = 'VIP')
              ->  Hash (rows=10000000) -> Seq Scan on users

时间降至 8 秒,但仍可优化。

Step 2:索引优化

目前的 orders 需要同时过滤 order_datestatus,现有单列索引不合理。创建复合索引:

CREATE INDEX idx_orders_date_status ON orders(order_date, status) INCLUDE (user_id, amount);
-- INCLUDE 使得 Index Only Scan 可直接获得 user_id 和 amount,无需回表

Step 3:重写查询,使用 CTE 先过滤 order 和 tag

WITH paid_orders AS MATERIALIZED (
  SELECT user_id, sum(amount) AS total
  FROM orders
  WHERE order_date >= now() - interval '30 days' AND status = 'paid'
  GROUP BY user_id
), vip_users AS MATERIALIZED (
  SELECT DISTINCT user_id FROM user_tags WHERE tag = 'VIP'
)
SELECT u.id, u.name, po.total
FROM users u
JOIN vip_users vu ON u.id = vu.user_id
JOIN paid_orders po ON u.id = po.user_id;
  • MATERIALIZED 强制 CTE 物化,避免重复扫描(每个 CTE 只被使用一次,但物化后数据量不大)。
  • 物化后分别建立哈希表,最终 Hash Join。

执行时间 1.2 秒

Step 4:扩展统计信息的应用

注意到 orders 表上 order_datestatus 有相关性:过去 30 天的订单中 95% 是 paid,而更早的订单 paid 比例较低。这导致优化器可能低估 paid 订单数量。创建扩展统计:

CREATE STATISTICS stts_order_date_status ON order_date, status FROM orders;
ANALYZE orders;

此后优化器在处理 WHERE order_date >= ... AND status = 'paid' 时能更准确估算行数,避免了因低估而错误选择 Nested Loop。

最终查询稳定在 300 毫秒 左右。


四、手把手诊断流程

当你遇到慢查询时,按以下步骤操作:

  1. 获取真实执行计划 EXPLAIN (ANALYZE, BUFFERS, TIMING) <query>; 关注实际行数(actual rows)与估算行数(rows)的差异。
  2. 检查统计信息新鲜度
    • pg_stat_user_tables 中的 last_analyze 时间。
    • 如果表有大量变更后未 analyze,执行 ANALYZE 再次测试。
  3. 对比估算 vs 实际
    • 对于过滤条件,检查 pg_stats 中对应列的 most_common_freqshistogram_bounds 是否合理。
    • 若多列条件误估,考虑创建扩展统计。
  4. 检查连接算法是否合理
    • Nested Loop 外层行数是否真的很小?
    • Hash Join 是否因 work_mem 不足而落盘?查看执行计划中的 Disk: xxx blocks
    • Merge Join 的排序代价是否过高?
  5. 索引审计
    • 连接键、过滤条件、排序列是否都有合适索引?
    • 是否可以利用覆盖索引(Index Only Scan)减少回表?
  6. 改写 SQL
    • 使用 CTE 调整连接顺序(物化关键中间结果)。
    • 使用窗口函数替代自连接/子查询(参考第二期)。

五、调优参数速查表

参数 作用 建议初始值(面向 OLAP)
work_mem 排序、哈希表、位图操作的内存 256MB ~ 1GB(根据内存调整)
shared_buffers 共享缓存 系统内存的 25%
effective_cache_size 操作系统文件缓存估算 系统内存的 50%~75%
random_page_cost 随机 I/O 代价(SSD 设为 1.1) 1.1(SSD)或 4(HDD)
seq_page_cost 顺序 I/O 代价 1.0
from_collapse_limit / join_collapse_limit 控制连接重写复杂度 默认 8,复杂查询可调大

注意:修改 work_mem 是针对每个操作(节点),不是整个查询,可适当调高但注意总内存消耗。


六、总结与下期预告

本期要点

  • 理解了三种连接算法的代价公式与适用场景,掌握了识别执行计划中的连接节点。
  • 深入统计信息的内部结构(MCV、直方图、相关度)以及如何通过 ALTER STATISTICS 提高精度。
  • 学习了扩展统计信息(多列 MCV、函数依赖)解决多列相关性的基数误判。
  • 通过真实案例,演练了从统计信息刷新、索引创建、SQL 改写,到最终性能提升 500 倍的全过程。

下期预告

第四期:物化视图、分区表与批量数据优化

  • 物化视图:何时以及如何刷新(REFRESH MATERIALIZED VIEW CONCURRENTLY),增量刷新技巧。
  • 分区表(声明式分区):分区裁剪、分区索引、与 BRIN 索引的联合使用。
  • 批量数据处理:INSERT ... ON CONFLICTCOPY 优化、DELETETRUNCATE 的选择。
  • 实战:千万级日志表的日分区 + 物化视图预聚合,查询从 30 秒到 0.1 秒。

欢迎在评论区分享你遇到的连接性能或统计信息相关的疑难案例,我们会在后续文章中选典型案例进行匿名诊断。

理解优化器的统计世界,你就掌握了性能调优的主动权。 我们第四期见。

Star
Donate
SQL 与查询优化(PostgreSQL 篇)· 第二期
Previous
SQL 与查询优化(PostgreSQL 篇)· 第四期
Next

Leave a comment

Registration is not required

Clyde Jin
302 Articles
0 Comments
0 Like
Recent Posts

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 […]

HiddenMerit Daily · Issue 57

📊 HiddenMerit Daily · Issue 57 Focus on Database Frontiers, Practical Insights for DBAs July 17, 2026 | 5 Selected Global Breaking News 01|CAICT Releases “Database Development Research Report (2026)”: China Market to Reach RMB 98 Billion by 2028, AI‑Native Becomes the Main Theme On July 9, at the 2026 Trustworthy Database Development Conference, the China Academy of Information and Communications Technology (CAICT) officially released the “Database Development Research Report (2026),” comprehensively revealing the latest landscape and evolution directions of the global and Chinese database markets. Key Data: Global Market: The global database market reached $131.6 billion in 2025 (approximately RMB 894.09 billion). Chinese Market: The Chinese database market reached $9.49 billion in 2025 (approximately RMB 64.48 billion), accounting for 7.2% of the global market. By 2028, the total Chinese database market is expected to reach RMB 97.974 billion, with a compound annual growth rate of 13.06% . Industry Landscape: There are 394 database product providers globally, with China and the US leading in vendor count. The number of global open‑source and closed‑source products is roughly balanced. Commercial databases account for over 70% in China, while the proportion of open‑source databases is increasing. After rapid growth from 2022 to 2024, […]
生成中...
扫描二维码
扫描二维码