排查 PostgreSQL 慢查询时,执行计划通常是最先看的材料,但执行计划里的成本数据是估算值,真实运行中执行器是否需要磁盘排序、哈希聚合是否分批、内存物化是不是频繁,单看 EXPLAIN 并不能完全确认。log_executor_stats 参数开启后,数据库会把执行器统计写入服务器日志,把执行阶段的排序、哈希、聚合和内存使用情况记录下来。这个参数和 log_statement_stats 不同,后者记录语句整体统计,前者只关注执行器内部,适合已经确认某条 SQL 较慢后进一步定位瓶颈。

要使用这个参数,可以先从会话级验证开始。对于已经抓到慢 SQL 的会话,执行 SET log_executor_stats = on; 就能让当前连接开始输出执行器统计。如果需要全局开启,可以在 postgresql.conf 中写入 log_executor_stats = on,或者使用 ALTER SYSTEM SET log_executor_stats = on; 后执行 SELECT pg_reload_conf(); 让配置生效。实际生产环境一般不建议长期全局开启,因为每条被记录的慢查询都会在日志中额外增加统计块,日志量会明显上升。
一、log_executor_stats 的作用与开启方式
PostgreSQL 提供了一组统计日志参数,包括 log_parser_stats、log_planner_stats、log_executor_stats 和 log_statement_stats。其中 log_statement_stats 会把语法解析、查询规划、执行器三个阶段的统计一次性输出,适合做粗粒度排查;而 log_executor_stats 只输出执行器相关数据,日志更聚焦,适合已经确定瓶颈在执行阶段时使用。两者同时打开时,log_statement_stats 会覆盖掉单阶段的输出,因此如果只需要执行器统计,就不要把 log_statement_stats 一并打开。
查看当前参数状态可以使用下面语句:
SHOW log_executor_stats; SET log_executor_stats = on; ALTER SYSTEM SET log_min_duration_statement = '1000ms'; SET log_min_duration_statement = '1000ms';
还可以通过 pg_settings 视图确认参数来源和是否需要重启:
SELECT name, setting, source, pending_restart FROM pg_settings WHERE name LIKE 'log_%stats';
log_executor_stats 本身属于普通用户可设置参数,但在全局打开前应当评估日志写入压力。建议先在一个业务低峰窗口内,通过 ALTER SYSTEM SET log_executor_stats = on; 开启,再结合 log_min_duration_statement = '1000ms' 只记录超过 1 秒的语句。这样既能捕获慢查询,又不会把所有快速语句都写进日志。
二、日志字段怎么看,如何定位瓶颈
当开启 log_executor_stats 后,触发慢查询阈值时,数据库日志中会出现类似下面的执行器统计信息:
LOG: duration: 1842.521 ms statement: SELECT category, count(*) FROM orders GROUP BY category;
DETAIL: EXECUTOR STATISTICS
Sort Method: external merge Disk: 1024kB
Buckets: 1024 Batches: 1 Memory Usage: 256kB
这里的 Sort Method: external merge 是关键线索。它说明排序操作无法在 work_mem 分配的内存中完成,已经被溢写到了一块或多块磁盘文件里。外排序的 I/O 成本通常远高于内存排序,日志中如果频繁出现 external merge,基本可以判断 work_mem 设置偏小,或者 SQL 本身要求的排序结果集过大。建议先查看 work_mem 的当前值,再结合排序数据量决定是否调大。
Buckets 和 Batches 则与哈希聚合、哈希连接有关。bucket 数量反映了哈希表的规模,batches 表示哈希操作被分成了几批处理。如果 batches 大于 1,说明内存不足以一次容纳哈希表,数据库需要把部分数据写入临时文件,分批处理。这种情况常见于 GROUP BY 后结果集较大、或连接表数据量很大时。遇到这种日志,可以尝试调大 work_mem,或者通过合适的索引降低进入聚合和哈希连接的数据量。
内存使用统计也能帮助判断执行器是否突破了预设限制。比如日志显示 Memory Usage 接近甚至超过 work_mem,同时伴随磁盘操作,就说明当前查询正处于内存与磁盘交换的临界点。此时不要盲目调大参数,而应优先考虑优化 SQL,例如增加合适的索引、减少排序字段数量、限制聚合前的结果集,才能从源头降低执行器压力。
三、配合 EXPLAIN 和 auto_explain 排查慢查询
单看 log_executor_stats 的日志,只能知道执行器有没有发生磁盘排序、哈希分批,却看不到具体是计划树中的哪个节点触发了这些操作。因此更实用的方式是把它与 EXPLAIN ANALYZE 结合使用。先通过日志确认某条 SQL 存在执行器异常,再回到客户端对同一条 SQL 执行 EXPLAIN (ANALYZE, BUFFERS, TIMING),从计划树中找到 Sort 或 HashAggregate 节点的实际耗时和 I/O 情况。
BEGIN; SET LOCAL log_executor_stats = on; SET LOCAL log_min_duration_statement = '1ms'; EXPLAIN (ANALYZE, BUFFERS, TIMING) SELECT category, count(*) FROM orders WHERE created_at >= now() - interval '7 days' GROUP BY category ORDER BY count(*) DESC LIMIT 20; COMMIT;
对于已经过去的慢查询,可能没有机会再手工执行 EXPLAIN ANALYZE,这时可以借助 auto_explain 扩展。它允许 PostgreSQL 在日志中自动记录超过阈值的语句执行计划,并且可以选择输出实际执行时间、缓冲区使用等信息。把 auto_explain 和 log_executor_stats 同时开启后,一条慢查询在日志中既能看到详细的计划树,也能看到执行器统计,定位效率会高很多。
shared_preload_libraries = 'auto_explain' auto_explain.log_min_duration = '1000ms' auto_explain.log_analyze = on auto_explain.log_buffers = on log_executor_stats = on
例如一条查询日志显示 external merge 用到了 2GB 磁盘空间,但 EXPLAIN 里的排序估算值并不大,说明实际输入到排序节点的行数被低估了。此时可以通过 auto_explain 输出的计划树确认排序节点上游是否有不合理的过滤条件失效,或者统计信息已经过期。很多情况下,执行 ANALYZE 更新统计信息后,规划器会重新选择更优路径,排序压力也随之下降。
如果确认排序本身无法避免,可以检查表上是否缺少与 ORDER BY 匹配的索引。对于本例中的 ORDER BY count(*) DESC,普通 B-tree 索引无法直接消除聚合后的排序,但可以尝试创建符合 GROUP BY 和 ORDER BY 顺序的覆盖索引,或者使用物化视图提前聚合。对分组字段建立合适索引,也能减少执行器需要处理的行数,从而降低排序和哈希压力。
四、避免把执行器统计当成全部监控
log_executor_stats 的价值在于深挖单条慢 SQL 的执行器行为,但它并不适合作为全局性能监控的日常入口。如果想知道数据库里哪些 SQL 最耗时、调用次数最高,应该优先使用 pg_stat_statements。这个扩展会汇总所有语句的执行次数、总耗时、平均耗时以及缓冲区命中情况,能快速找出最值得优化的目标。
CREATE EXTENSION IF NOT EXISTS pg_stat_statements; SELECT query, calls, mean_exec_time, max_exec_time FROM pg_stat_statements ORDER BY mean_exec_time DESC LIMIT 10;
定位到具体 SQL 后,再对它开启 log_executor_stats,才是更合理的排查顺序。生产环境中如果长期全局开启该参数,日志系统很快就会堆积大量执行器统计,尤其是高并发写入场景下,日志写入本身也可能成为新的瓶颈。建议在日常监控中保持关闭,只有排查问题时使用 SET LOCAL 只对当前事务生效,或临时调整 log_min_duration_statement 阈值,减少无用日志。
SET log_executor_stats = on; SET log_min_duration_statement = '2000ms';
有些慢查询看起来执行器统计很干净,没有磁盘排序,也没有哈希分批,但客户端仍然反馈慢。这时需要跳出执行器视角,查看连接是否在等待锁、等待 WAL 写入或等待网络响应。pg_stat_activity 中的 wait_event_type 和 wait_event 可以反映当前会话等待状态,这类问题不会出现在 log_executor_stats 日志里。
SELECT pid, state, wait_event_type, wait_event, query FROM pg_stat_activity WHERE state <> 'idle' AND backend_type = 'client backend';
总的来说,log_executor_stats 是 PostgreSQL 慢查询优化中一个很实用的定位工具,它把隐藏在总耗时背后的执行器开销拆解出来,让排序溢出、哈希分批、内存不足等问题更容易被发现。正确用法是对整体性能问题先用 pg_stat_statements 做粗筛,再对目标 SQL 临时开启 log_executor_stats,结合 EXPLAIN ANALYZE 或 auto_explain 做精细定位。这样既能快速找到瓶颈,又能避免长期开启带来的日志噪音和额外写入压力。
PostgreSQL慢查询优化log_executor_stats执行器统计修改时间:2026-09-18 13:55:31