PostgreSQL 有上百个 GUC(Grand Unified Configuration)参数,大多数面向生产运维,比如 shared_buffers、work_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-backend 或 superuser,需要超级权限才能改。下面挑几个最实用的讲。
看懂查询计划的每一步变换
一条 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 开发者的内部工具,文档里往往只有一句话描述。使用时注意:
- 日志膨胀风险——
debug_print_*会把每条查询的内部树结构写入日志,高并发场景下磁盘会迅速填满。只在排查特定问题时短暂开启,排查完立即SET ... = off。 - 需要 superuser——大部分
debug_*参数的 context 是superuser或superuser-backend,普通业务账号无权修改。如果团队没有 superuser 访问,需要运维配合。 - 不要用于生产调优——这些参数不是性能优化工具,是诊断工具。
debug_force_parallel_mode在生产上开启会破坏成本模型的决策逻辑。 - 版本差异——不同 PG 版本的
debug_*参数集合有变化。升级前重新跑一遍上面的pg_settings查询,确认你要用的参数还存在。 - 结合
log_min_messages——debug 输出的日志级别通常是DEBUG1到DEBUG5,确保log_min_messages设到了足够低的级别才能看到:
-- 临时降低日志级别
SET log_min_messages = DEBUG1;
-- ... 开启你的 debug_* 参数 ...
-- 排查完毕恢复
SET log_min_messages = WARNING;
下次遇到 planner 行为诡异、并行查询结果不一致、或死锁难以复现的情况,先别急着翻源码——试试这些 debug_* 开关,它们是 PG 留给你的后门。