PostgreSQL 的 debug_* 参数:把内部测试开关当调试工具用

2026-06-11 26 预计阅读时间: 1 分钟
来源: postgr.es AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:7 分钟

PostgreSQL 有上百个 GUC(Grand Unified Configuration)参数,大多数面向生产运维,比如 shared_bufferswork_mem。但有一小撮参数以 debug_ 开头,它们原本是 PG 自身的开发测试机制——开发者用来验证 planner、deadlock detector、parallel execution 等子系统是否正常工作。这些参数被暴露为运行时配置,意味着你可以在会话里随时 SET,拿来窥探数据库内部行为。

这些参数都在哪

pg_settings 视图里直接过滤:

SELECT name, setting, short_desc, context
  FROM pg_settings
 WHERE name LIKE 'debug_%'
 ORDER BY name;

你会看到大约十几个参数,context 列标注了生效范围——大部分是 superuser-backendsuperuser,需要超级权限才能改。下面挑几个最实用的讲。

看懂查询计划的每一步变换

一条 SQL 从文本到最终执行计划,要经历 parse → rewrite → plan 三个阶段。日常用的 EXPLAIN 只展示最终计划,中间过程被吞掉了。debug_print_* 系列可以把每个阶段的内部表示打印到服务器日志:

参数 打印内容
debug_print_parse 解析树(Parse tree)
debug_print_rewritten 重写后的查询树(Rewritten query tree)
debug_print_prelim_plan 初步计划(未优化的 Path → Plan 转换前)
debug_print_plan 最终执行计划

配合 debug_pretty_print = on,输出会格式化缩进,否则是一坨扁平文本。

实际操作:

-- 在当前会话打开(需要 superuser)
SET debug_print_plan = on;
SET debug_pretty_print = on;

-- 执行一条简单查询
SELECT 1;

-- 关掉,避免日志膨胀
SET debug_print_plan = off;

然后去日志文件查看。日志路径取决于 log_directory,默认在 $PGDATA/log/ 下。用这条命令快速定位:

# 找到最近的日志文件
ls -lt $PGDATA/log/ | head -5

# 过滤 debug 输出(关键词取决于 PG 版本,一般包含 "plan:" 或 "QUERY PLAN")
grep -i "plan:" $PGDATA/log/postgresql-$(date +%Y-%m-%d)*.log | tail -20

你会看到类似这样的结构化输出(简化示意):

{PLANNEDSTMT
   :commandType 1
   :queryId 0
   :planTree
      {RESULT
         :startup_cost 0.00
         :total_cost 0.01
         :plan_rows 1
         :plan_width 4
      }
}

这对理解视图展开、规则重写、分区表路由特别有用。比如你怀疑某个视图的 WHERE 子句被重写推到了错误的位置,打开 debug_print_rewritten 就能看到重写后的完整 Query tree,确认谓词到底挂在哪个 RangeTblEntry 上。

强制并行执行来测试并行逻辑

debug_force_parallel_mode 是另一个值得关注的参数。正常情况下,PG 的并行查询有门槛——表太小、查询太简单都不会触发并行。但调试并行相关 bug 时,你需要确保并行路径一定被选中。

-- 强制并行模式(1 = force parallel, 0 = off)
SET debug_force_parallel_mode = 1;
-- 同时确保并行 worker 可用
SET max_parallel_workers_per_gather = 4;

-- 执行查询
SELECT count(*) FROM large_table;

-- 恢复
SET debug_force_parallel_mode = 0;

设为 1 后,即使查询成本低于并行阈值,Gather 节点也会被强制插入计划。这在排查并行执行下的数据一致性、锁行为、worker 进程异常时非常方便。注意:生产环境绝对不要开——它会绕过成本评估,让本不该并行的查询强行并行,反而拖慢性能。

死锁检测的额外诊断

debug_deadlocks 打开后,死锁检测逻辑会在日志里输出更详细的等待图信息:

SET debug_deadlocks = on;

然后在两个会话里故意制造死锁:

# 会话 A
psql -U superuser -d testdb
BEGIN;
UPDATE t1 SET val = 'a' WHERE id = 1;

# 会话 B(另一个终端)
psql -U superuser -d testdb
BEGIN;
UPDATE t1 SET val = 'b' WHERE id = 2;
-- 然后回头更新 id = 1,等会话 A 的锁

# 会话 A 再更新 id = 2,触发死锁
UPDATE t1 SET val = 'c' WHERE id = 2;

日志里除了常规的 "process detected deadlock" 消息,还会多出锁等待链的详细结构,帮你确认到底是哪两个事务、哪两行记录构成了环。

实用检查清单

这些参数本质上是 PG 开发者的内部工具,文档里往往只有一句话描述。使用时注意:

  1. 日志膨胀风险——debug_print_* 会把每条查询的内部树结构写入日志,高并发场景下磁盘会迅速填满。只在排查特定问题时短暂开启,排查完立即 SET ... = off
  2. 需要 superuser——大部分 debug_* 参数的 context 是 superusersuperuser-backend,普通业务账号无权修改。如果团队没有 superuser 访问,需要运维配合。
  3. 不要用于生产调优——这些参数不是性能优化工具,是诊断工具。debug_force_parallel_mode 在生产上开启会破坏成本模型的决策逻辑。
  4. 版本差异——不同 PG 版本的 debug_* 参数集合有变化。升级前重新跑一遍上面的 pg_settings 查询,确认你要用的参数还存在。
  5. 结合 log_min_messages——debug 输出的日志级别通常是 DEBUG1DEBUG5,确保 log_min_messages 设到了足够低的级别才能看到:
-- 临时降低日志级别
SET log_min_messages = DEBUG1;
-- ... 开启你的 debug_* 参数 ...
-- 排查完毕恢复
SET log_min_messages = WARNING;

下次遇到 planner 行为诡异、并行查询结果不一致、或死锁难以复现的情况,先别急着翻源码——试试这些 debug_* 开关,它们是 PG 留给你的后门。


相关推荐