Skip to content

慢查询日志(log_min_duration_statement) ​

数据库变慢的时候,最先要回答的问题永远是:「到底慢在哪条 SQL 上?」pg_stat_activity 能看到当下正在执行的语句,pg_stat_statements 能看到历史累计的统计,但它们都不回答「某一次具体执行到底花了多久、那条 SQL 原文是什么」。慢查询日志就是回答这个问题的第一现场:PostgreSQL 在语句执行完成后,把耗时超过阈值的语句连同它的执行时长一起写进服务器日志,形成一份「案发记录」。

承载这个能力的核心参数是 log_min_duration_statement。它的语义极其简单:语句执行时间达到或超过指定毫秒数时,把该语句的耗时与文本记录到日志。设为 0 则记录所有语句的耗时,-1(默认值)则完全不记录。正是因为语义简单、无需扩展、随内核发行,它几乎是所有 PostgreSQL 慢查询治理工作的起点——多数第三方监控方案(pgbadger、pganalyze、各种日志采集管道)的输入,都是它产出的日志。

但「简单」不等于「随便开」。这条参数背后有一整套配套机制:它与 log_statement 的分工与重叠、PostgreSQL 12 引入的采样记录(log_min_duration_sample / log_statement_sample_rate)、PostgreSQL 13 引入的参数截断(log_parameter_max_length)、按角色与按数据库的细粒度覆盖,以及对性能和磁盘的真实影响。本页把这些机制逐个讲透,并给出「定位 1 秒以上 SQL」的完整实战流程。

前置知识

建议先阅读:JSON 日志(PostgreSQL 15+)

基础概念 ​

什么算「慢查询」 ​

「慢查询」没有绝对定义,只有相对于业务预期的定义。同样是 800ms,对内部管理后台的报表查询可能完全可接受,对结账接口的回调链路可能已经是事故。所以在动手之前,先建立一个观念:阈值是业务决策,不是技术决策。DBA 的职责是把业务给的 SLO(例如「95% 的查询应在 200ms 内完成」)翻译成日志参数与告警规则。

从技术视角,一个语句的总耗时由多个部分组成:

组成部分说明典型占比
排队等待连接池排队、锁等待变化极大
解析与规划parse / rewrite / plan简单语句极小,复杂语句可观
执行真正的取数、计算、写回通常是主体
提交WAL 刷盘、事务提交写密集场景可观

log_min_duration_statement 记录的是整个语句从开始到完成的耗时,包含上述所有部分。这意味着一条被记为「慢」的语句,慢的原因未必在执行计划上,也可能在锁上——这一点在解读日志时要始终记住。

参数家族一览 ​

围绕「语句要不要进日志」,PostgreSQL 提供了一组分工明确的参数。先总览,后面逐个展开:

参数版本作用默认值
log_min_duration_statement早期即有耗时 ≥ 阈值的语句:记录耗时 + 文本-1(关闭)
log_duration早期即有所有完成的语句:只记耗时,不记文本off
log_statement早期即有按类型(ddl / mod / all)记录语句文本,不看耗时none
log_min_duration_samplePG12+耗时 ≥ 阈值的语句按比例采样记录-1(关闭)
log_statement_sample_ratePG12+采样比例,0.0 ~ 1.01.0
log_transaction_sample_ratePG12+按事务采样:命中的事务内所有语句都记录0
log_parameter_max_lengthPG13+语句日志中绑定参数值的截断长度(字节)-1(不截断)
log_parameter_max_length_on_errorPG13+报错消息中绑定参数值的截断长度(字节)0(不记录)

记忆主线只有一条:log_min_duration_statement 是「按耗时记录」,其余参数要么是它的替代(log_duration)、要么是它的补充(采样、参数截断)、要么是另一条独立管道(log_statement)。

版本差异速查 ​

本页涉及的参数分属不同版本,维护多版本环境时按这张表核对:

能力最低版本说明
log_min_duration_statement 基本语义8.x 时代即有所有生产版本可用
log_min_duration_sample + log_statement_sample_ratePG12语句级采样
log_transaction_sample_ratePG12事务级采样
log_parameter_max_lengthPG13语句日志中绑定参数截断
log_parameter_max_length_on_errorPG13错误消息中参数截断(默认 0)
jsonlog 日志格式PG15结构化日志,解析成本最低

PG12 之前的环境没有采样与事务采样能力,日志量只能靠「提高阈值 + 角色级覆盖」来控制;PG13 之前没有参数截断,敏感参数值会完整进入日志,这两点在老版本升级评估时要列为风险项。

慢查询定位的三层工具 ​

在 PostgreSQL 的工具箱里,慢查询定位手段大致分三层,本节四页正好按这个顺序展开:

层次工具回答的问题依赖
现场层慢查询日志 / auto_explain哪条 SQL、哪一次执行、当时计划长什么样仅内核参数
统计层pg_stat_statements每类 SQL 累计耗时、调用次数、平均耗时扩展 + 预加载
视图层pg_stat_activity / pg_locks此刻谁在执行、谁在等锁内置视图

三层各有盲区:日志看不到「没超过阈值」的语句;统计层看不到单次执行的参数与计划;视图层只反映「当下」。成熟的监控体系会让三层同时在线,互相印证。本页解决的是第一层中最基础、也是唯一在「零扩展」环境下就能用的手段——慢查询日志。

duration 日志长什么样 ​

开启之后,日志里会出现这样一类的条目(stderr / csvlog / jsonlog 的字段不同,内容结构一致):

text
2026-10-07 10:15:32.881 CST [28312] LOG:  duration: 1423.556 ms  statement: SELECT o.id, o.amount
        FROM orders o JOIN users u ON u.id = o.user_id
        WHERE o.created_at > now() - interval '1 hour' AND u.region = 'cn-north';

它由三部分构成:

  1. 级别与耗时:LOG: duration: 1423.556 ms——注意单位恒为毫秒,带小数。
  2. 标签:statement:,表示后面跟的是语句原文。
  3. 语句文本:服务端收到的 SQL(绑定参数已被代入或截断,取决于版本与 log_parameter_max_length)。

如果是走扩展查询协议(extended query protocol,绝大多数驱动都如此)的语句,标签与耗时行为略有不同,详见后文「扩展查询协议下的行为」。

语法与用法 ​

基本配置 ​

conf
# postgresql.conf —— 最常见的起步配置

# 执行时间达到或超过 1 秒的语句,记录耗时与文本
log_min_duration_statement = 1000

# 强烈建议:日志行前缀里带上进程号与会话号,
# 官方明确建议用它来关联 duration 行与语句文本行
log_line_prefix = '%m [%p] %q%u@%d '

log_min_duration_statement 的取值规则:

  • 正整数:单位毫秒(可显式写 1s、250ms,未带单位时按毫秒解释)。
  • 0:记录所有完成语句的耗时与文本,等价于全量语句日志(含 duration)。
  • -1(默认):完全关闭。

它是 superuser 级别的参数:只有超级用户(或拥有相应 SET 权限的用户)可以修改。修改后 reload 即生效,不需要重启,这是它适合线上动态调整的前提。

bash
# 三种在线修改方式,任选其一
postgres=# SET log_min_duration_statement = '1s';          -- 仅当前会话(需 superuser)
postgres=# ALTER SYSTEM SET log_min_duration_statement = '1s';  -- 全局,reload 生效
$ psql -c "SELECT pg_reload_conf();"                       -- 让 ALTER SYSTEM 生效
sql
-- 查看当前值
SHOW log_min_duration_statement;
SELECT name, setting, unit, source, pending_restart
FROM pg_settings
WHERE name = 'log_min_duration_statement';

按数据库 / 按角色覆盖 ​

全局一刀切的阈值往往不合适:OLTP 核心库希望阈值低到 200ms,而报表库 5 秒都正常。PostgreSQL 允许对参数做按库、按角色的覆盖:

sql
-- 报表账号放宽到 10 秒
ALTER ROLE report_user SET log_min_duration_statement = '10s';

-- 分析库放宽到 5 秒
ALTER DATABASE analytics SET log_min_duration_statement = '5s';

-- 核心交易账号收紧到 200ms
ALTER ROLE app_rw SET log_min_duration_statement = '200ms';

-- 查看某个角色的覆盖值
SELECT rolname, rolconfig FROM pg_roles WHERE rolname = 'report_user';

覆盖的优先级是:会话级 SET > ALTER ROLE / ALTER DATABASE 设置 > postgresql.conf。ALTER ROLE ... SET 写入系统目录,对所有后续新连接生效,已有连接不受影响。

云托管环境下的开启方式 ​

自建库可以改 postgresql.conf,云托管库(RDS、Cloud SQL 等)则通过参数组 / 参数模板下发,语义完全一致:

  1. 在控制台找到参数组中的 log_min_duration_statement,设为目标毫秒数。
  2. 部分云厂商要求同时开启日志收集开关(如 RDS 的日志导出到 CloudWatch、阿里云的 SQL 洞察开关),否则日志只落在实例本地且随实例释放。
  3. 应用参数组后确认是否需要重启——该参数本身是 reload 即生效,但同一参数组里其他参数可能引入重启要求,下发前逐项核对。
  4. 云厂商的「慢 SQL 分析」「慢日志查询」功能,底层多数就是该参数的产物,阈值要与控制台功能的最小支持值对齐。

具体参数名与界面以所用云厂商文档为准,但底层的日志格式与解读方法与本页完全相同——这正是掌握内核参数的价值:换任何托管环境都通用。

与 log_statement 的关系(官方说明) ​

log_statement 是另一条独立的日志管道:它按类型(none / ddl / mod / all)记录语句文本,完全不关心耗时。两者并存时的行为,官方文档有明确说明,这里原样拆解:

官方文档的两条关键说明

  1. 文本不重复:官方在 log_min_duration_statement 的说明中注明——当它与 log_statement 同时使用时,「由 log_statement 记录的语句文本,不会在 duration 日志消息中重复出现」。也就是说,一条同时命中两条管道的语句,日志里是「一条语句文本行 + 一条 duration 行」,而不是两条都带全文。官方同时建议:不使用 syslog 时,应在 log_line_prefix 中记录 PID 或会话 ID,以便用进程号/会话号把这两条消息关联起来。
  2. 语法错误不在 log_statement 覆盖范围:官方在 log_statement 的说明中注明——即使设为 all,含简单语法错误的语句也不会被记录,因为日志在完成基本解析、确定语句类型之后才会发出;要捕获这类失败语句,应把 log_min_error_statement 设为 ERROR(或更低)。

推论值得写进团队约定:社区里常说「log_statement = all 时 log_min_duration_statement 意义不大」,这是对上述第一条说明的口语化转述——全量文本已经由 log_statement 记录,duration 行里不再带文本,你只能拿到「耗时」而拿不到「这条耗时对应哪条 SQL」(除非靠 PID 关联)。对慢查询定位而言,正确姿势是用 log_min_duration_statement 承担记录职责,而不是把 log_statement 开成 all:后者记录的是全部语句(绝大多数是快的),日志量与性能代价大得多。

conf
# 反面教材:为了看慢查询把全量语句日志打开
log_statement = all            # ❌ 记录所有语句文本,日志量巨大

# 正确做法:只按耗时记录,天然只留慢的
log_min_duration_statement = 1000   # ✅

采样记录(PG12+) ​

高并发场景下,1 秒以上的语句可能每秒就有几百条,全量记录日志本身会成为新的瓶颈。PostgreSQL 12 提供了采样机制:

conf
# 超过 500ms 的语句,按 10% 的比例抽样记录
log_min_duration_sample = 500
log_statement_sample_rate = 0.1

两条参数的分工:

  • log_min_duration_sample:定义「进入采样候选池」的耗时下限,语义与 log_min_duration_statement 完全一致,只是命中的语句不保证记录。
  • log_statement_sample_rate:0.0 ~ 1.0 的小数,表示候选池中有多大比例真正落日志。采样是随机的,0.5 意味着每条候选语句各有五成概率被记录。

官方文档明确了两者的优先级:log_min_duration_statement 优先于 log_min_duration_sample——超过 log_min_duration_statement 阈值的语句必然记录,不参与采样。所以典型的组合用法是「双阈值」:

conf
# 5 秒以上:全量记录(严重慢查询一条都不许漏)
log_min_duration_statement = 5000

# 500ms ~ 5s:按 5% 采样(中等慢查询留样本即可)
log_min_duration_sample = 500
log_statement_sample_rate = 0.05

同版本还有 log_transaction_sample_rate:按事务采样,命中的事务里所有语句(无论快慢)全部记录。它适合排查「一个事务里到底执行了什么」的问题,代价是日志量不可预估,需谨慎使用。

log_transaction_sample_rate 详解 ​

这是 PG12 引入的第三种采样维度,值得单独讲。前两种采样(log_min_duration_sample)的对象是「单条语句」,而 log_transaction_sample_rate 的对象是「事务」:每个新事务以指定概率被选中,被选中事务内的全部语句都被完整记录(文本 + 各自耗时),与语句自身快慢无关。

conf
# 1% 的新事务被全量记录
log_transaction_sample_rate = 0.01
采样参数采样对象记录范围典型用途
log_min_duration_sample单条语句达到阈值的语句,按比例记录高并发下降日志量
log_statement_sample_rate——配合上一条,定义比例——
log_transaction_sample_rate事务命中事务的所有语句还原一个业务事务的完整 SQL 序列

它解决的问题很具体:当一个业务事务(例如「下单 + 扣库存 + 写流水」)整体变慢时,单条语句的日志只能看到碎片,而事务采样能看到完整序列与次序,方便对照代码里的事务边界。使用要点:

  • 采样命中与语句耗时无关,因此快语句也会大量进日志,日志量 = 平均事务语句数 × 事务速率 × 采样率,务必先按这个公式估容量。
  • 0(默认)表示关闭;设为 1.0 则所有事务全量记录,仅限分钟级诊断窗口使用。
  • 它与其他 duration 参数可以叠加,注意同一条语句可能同时命中多条管道,解析脚本要做去重。

参数截断与脱敏(PG13+) ​

duration 日志里的语句文本如果来自扩展协议,会包含绑定参数的实际值。这既是排障的便利(能看到真实查询条件),也是风险(可能泄露敏感数据)。PostgreSQL 13 起提供两个截断参数:

conf
# 非错误类语句日志:绑定参数值最多记 1024 字节,超出截断
log_parameter_max_length = 1024

# 错误消息中的参数值:默认 0(完全不记录)
log_parameter_max_length_on_error = 0
  • log_parameter_max_length 只影响 log_statement、log_min_duration_statement 等语句类日志;-1(默认)完整记录,0 不记录参数值,正数按字节截断。官方同时注明:非零值会带来额外开销,尤其当参数以二进制形式传输时。
  • log_parameter_max_length_on_error 只影响错误消息;默认 0 表示错误消息里不出现参数值;-1 完整记录。

扩展查询协议下的行为 ​

现代驱动(JDBC、psycopg、pgx 等)几乎都走扩展查询协议,语句被拆成 Parse / Bind / Execute 三个阶段。官方文档明确:三个阶段的耗时会分别独立记录。日志中对应的形态是 duration: ... ms statement: ...(解析阶段)与 duration: ... ms execute <名称>: ...(执行阶段,参数值随行)。解读日志时不要把 Parse 与 Execute 两条记录误当成两条 SQL。

详细说明 ​

duration 的时间从哪一毫秒开始算 ​

log_min_duration_statement 统计的时长,覆盖从语句开始执行到执行完成的全部时间,包括锁等待。一个直接推论:日志里一条 8 秒的 UPDATE,可能 7.9 秒都在等行锁,真正执行只花了 100ms。所以慢查询日志是「发现」工具,不是「确诊」工具——确诊需要结合 pg_locks、pg_stat_activity 或执行计划。发现异常 duration 后的标准动作是:先用 log_line_prefix 里的时间戳与 PID 去关联同时间窗的锁等待与活动会话,再决定要不要进入 EXPLAIN ANALYZE 环节。

日志级别的选择 ​

duration 日志以 LOG 级别写入服务器日志,受 log_min_messages 管辖,默认配置即可看到。要注意它与 log_min_error_statement 的关系:后者控制的是出错语句要不要连同 SQL 一起记录(默认 ERROR,即出错的语句会记录)。两者互补:一个记录「慢但成功」的语句,一个记录「失败」的语句,覆盖了需要人工介入的两类查询。

不同日志格式下的字段对照 ​

同一份 duration 信息,在三种日志格式下的呈现不同,解析脚本要分别处理:

信息stderr 文本日志csvlogjsonlog(PG15+)
耗时duration: 1423.556 ms 混在 message 里message 列中的 duration: 文本message 字段中的 duration: 文本
语句文本statement: 之后的部分query 列(独立字段)statement 字段
时间戳前缀 %m(格式依赖 log_line_prefix)log_time 列timestamp 字段
进程号前缀 [%p]backend_pid 列pid 字段
用户 / 库前缀 %u@%duser_name / database_name 列user / dbname 字段

关键差异在语句文本是否为独立字段:stderr 日志需要正则从 message 里截取 statement: 之后的内容(多行语句还要拼接续行);csvlog 与 jsonlog 的语句文本是独立字段,天然规避了多行与转义问题。这也是官方提供 csvlog/jsonlog 的初衷——「方便把日志装载进程序」。

与日志采集管道的集成 ​

生产环境几乎不会直接登录服务器看日志,标准链路是把 PostgreSQL 日志接入集中采集:

text
PostgreSQL (jsonlog) → Filebeat / Fluent Bit / Vector → Kafka / Loki / ES → Grafana / Kibana

集成时的四个要点:

  1. 多行合并:stderr 格式下一条日志可能占多行(长 SQL 折行),采集端要配置多行合并规则;jsonlog 天然单行,推荐优先使用。
  2. 时间字段标准化:采集管道的时间戳与 PostgreSQL 的 log_timezone 要对齐,跨时区部署时尤其注意。
  3. 按 severity 分流:duration 日志的级别是 LOG,与 ERROR/FATAL 分开索引,避免慢查询淹没报错。
  4. 保留敏感过滤:在采集端再加一道脱敏(正则替换手机号、身份证号等),双保险于 log_parameter_max_length。

采集之后,就能在 Grafana 里做「每分钟慢查询条数」曲线、按账号/库的下钻视图——这就把本页的日志能力接入了后文的 Prometheus + Grafana 监控体系。

用 pgBadger 做日志聚合 ​

手工看日志只能救火,聚合成报表才能治本。pgBadger 是最常用的 PostgreSQL 日志分析器,直接消费 log_min_duration_statement 的产出:

bash
# 生成某一天的 HTML 报表
pgbadger -I -f jsonlog \
  /var/lib/postgresql/log/postgresql-2026-10-07_*.json \
  -o /var/www/report-20261007.html

# 增量分析:按小时追加到同一个累计报告
pgbadger -I --incremental \
  -O /var/www/pgbadger/ \
  /var/lib/postgresql/log/postgresql-*.json

报表里与本页直接相关的视图包括:按耗时分布的直方图、最慢语句 Top、出现频次 Top、按会话/账号聚合的耗时占比。把「每周 pgbadger 报表」固化成例行巡检项,是成本最低的慢查询治理抓手。

长事务与空闲事务的盲区 ​

duration 日志按「语句完成」记录,因此它有两个天然盲区,需要配合其他视图补位:

  • 长时间运行的进行中语句:语句还没执行完就不会产生 duration 记录。一条已经跑了 20 分钟的查询,在它结束前日志里毫无痕迹。此时要靠 pg_stat_activity 的 state = 'active' 与 xact_start/query_start 实时发现。
  • 空闲中的长事务(state = 'idle in transaction'):事务开着不提交,阻塞别人却自己不产生任何语句日志。这类问题要靠 pg_stat_activity 的 xact_start 超时告警来覆盖(连接数监控页有现成 SQL)。

所以慢查询日志的定位是「事后取证 + 常态发现」,与视图层的「实时发现」互为补充,两者都要有。

与 log_duration 的对比 ​

log_duration 是更朴素的兄弟参数:开启后,每一条完成的语句都记一行 duration,但不记语句文本。它的唯一价值是统计全量耗时分布,定位能力几乎为零(只有耗时没有 SQL)。它与 log_min_duration_statement 同用时,同一条语句可能出现两条 duration 记录(一行来自 log_duration 无文本,一行来自 log_min_duration_statement 带文本),解读日志时需注意去重。实践中除了临时诊断连接池抖动,一般不建议长期开启 log_duration。

日志条目的完整解读 ​

一条完整的慢查询日志,值得逐字段榨取信息:

text
2026-10-07 10:15:32.881 CST [28312] app_rw@shopdb LOG:  duration: 1423.556 ms  statement: SELECT ... ;
字段来源用途
2026-10-07 10:15:32.881 CSTlog_line_prefix 的 %m定位时间窗,与告警、慢查询曲线对齐
[28312]%p(进程号)关联同会话的其他日志行(官方建议的关联方式)
app_rw@shopdb%u@%d判断来自哪个账号与库,直接指向 ALTER ROLE ... SET 的调优对象
duration: 1423.556 ms固定格式与阈值比较,判断严重程度
statement: ...固定格式语句原文,进入 EXPLAIN 复现的输入

如果日志走了 csvlog / jsonlog,这些字段会被拆成独立列(duration、query、backend_pid 等),解析脚本不用再做正则切分——这正是前面《JSON 日志》一页推荐 jsonlog 的原因。

性能开销的真实量级 ​

官方文档对 log_min_duration_statement 的评价是「有助于发现未优化查询,但短查询多时可能有明显开销,需谨慎」。开销来自两处:

  1. 计时本身:每条语句都要在结束时刻取一次系统时钟并比较阈值。相对语句自身耗时,这一步通常可以忽略。
  2. 格式化与写日志:只有超过阈值的语句才会格式化文本并写日志,所以开销与「慢查询数量」成正比,而不是与「总查询数量」成正比——这正是它优于 log_statement = all 的地方。

由此得到的经验法则:阈值只要设得足够高(只捕获真正异常的语句),长期开启的代价接近于零。真正需要警惕的是把阈值设成 0(全量记录)或极低值(如 1ms),那等于把日志系统当成了审计系统用。

磁盘与日志量的容量估算 ​

慢查询日志会写满磁盘,是这类事故最常见的形式。上线前做一次量级估算:

text
单条日志大小 ≈ 语句文本长度 + 前缀长度(约 60~150 字节)
日志总量/天 ≈ 慢查询 QPS 峰值 × 86400 × 单条大小 × 波动系数

配合日志侧的三个参数做兜底(详见《日志轮转与归档》一页):

conf
log_directory = 'log'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_rotation_size = 100MB        # 单文件上限
log_rotation_age = 1d            # 最长保留周期

再叠加前面介绍的采样机制(log_min_duration_sample),可以把日志量压到可控范围。

按角色覆盖的进阶用法 ​

ALTER ROLE ... SET 不只用于放宽阈值,还有两个高频进阶用法:

sql
-- 1. 应用账号收紧,运维只读账号放宽
ALTER ROLE app_rw SET log_min_duration_statement = '200ms';
ALTER ROLE report_ro SET log_min_duration_statement = '10s';

-- 2. 已有会话不生效的问题:ALTER ROLE 只影响新连接。
--    若要立即让某个活跃会话生效,需由 superuser 对其执行:
SELECT pg_terminate_backend(pid) FROM pg_stat_activity
WHERE usename = 'app_rw' AND application_name = 'legacy-worker';
-- (由连接池重新拉起连接后,新阈值生效)

注意「会话内 SET 可以覆盖一切」:应用代码里如果执行了 SET log_min_duration_statement = -1,角色级配置会被盖掉。对不受控的第三方应用,可在 postgresql.conf 里用 ALTER ROLE ... SET 之外的思路——把它列入代码评审检查项。

与 pg_stat_statements 的配合 ​

日志记录「单次执行的现场」,pg_stat_statements 记录「累计的统计」,两者是互补关系而非替代关系:

  • 日志告诉你这一条SQL 这一次跑了多久、当时的原文是什么——适合取证与复现。
  • pg_stat_statements 告诉你每一类SQL 累计跑了多久、平均多少、调用多少次——适合找 Top SQL。

配合方式通常有两种:其一,用 pg_stat_statements 圈定总耗时最高的几类语句,再用日志的原始记录确认其真实文本与参数形态;其二,反过来,从日志里发现单次异常,再到 pg_stat_statements 里看它是不是常态。pg_stat_statements.track 配置决定了哪些语句进入统计(默认 top,只统计顶层语句),与 log_min_duration_statement 无需特殊配置即可并存。两页配合使用的完整工作流,见本节第三页《pg_stat_statements 慢查询分析》。

阈值怎么选:从 SLO 到日志参数 ​

「设多少合适」是提问最多的问题,答案要倒推:先有业务 SLO,再有告警线,最后是日志阈值。一个可复用的推导过程:

text
业务 SLO:核心接口 P99 延迟 < 800ms
→ 接口里数据库耗时占比假设 ≤ 50%
→ 数据库侧预算:单查询 P99 < 400ms
→ 日志阈值取预算的一半:200ms(越界即被记录,留出诊断余量)
→ 严重阈值:P99 的 5 倍,即 1s(触发即时告警)

对应配置:

conf
log_min_duration_statement = 1000   # 严重线,全量 + 告警
log_min_duration_sample = 200       # 预警线,采样观察
log_statement_sample_rate = 0.05

两个工程经验:其一,阈值宁低勿高——初期日志多一点没关系,观察一周后再收紧;阈值过高会让偶发的慢查询(恰恰是最有诊断价值的尾部样本)漏网。其二,阈值要随业务演进复审,流量翻倍后原本合理的阈值可能让日志量翻倍。

从日志到结论:解读时的常见误区 ​

误区一:把 duration 当执行计划耗时。duration 含锁等待与排队,慢的原因可能在别人身上。

误区二:只看平均不看分布。日志里同一条语句模板反复出现但耗时差异巨大,往往意味着参数不同导致计划不同(绑定参数嗅探问题),这正是 log_parameter_max_length 保留参数值的价值所在。

误区三:把语法解析阶段的记录当成独立慢查询。扩展协议下 Parse/Bind/Execute 各有一条 duration 记录,串联 PID 后应当作一次执行。

误区四:忘了时区。log_timezone 与应用时区不一致时,按时间窗关联日志与业务监控会对不上号。

实战示例 ​

示例一:定位 1 秒以上的 SQL ​

目标:给一台生产库开启慢查询捕获,阈值 1 秒,并验证日志产出。

bash
# 1. 在线开启(superuser 执行)
psql -U postgres -c "ALTER SYSTEM SET log_min_duration_statement = '1s';"
psql -U postgres -c "SELECT pg_reload_conf();"

# 2. 确认生效(source 应显示为 configuration file / override)
psql -U postgres -c "SELECT name, setting, source FROM pg_settings WHERE name = 'log_min_duration_statement';"
text
           name            | setting |         source
---------------------------+---------+--------------------------
 log_min_duration_statement| 1000    | configuration file
(1 row)
sql
-- 3. 制造一条慢查询验证
SELECT pg_sleep(1.5);
text
2026-10-07 10:31:02.114 CST [31980] postgres@postgres LOG:  duration: 1502.318 ms  statement: SELECT pg_sleep(1.5);

拿到第一条日志后,把它与 pg_stat_statements 的 Top SQL 相互印证,即可进入正式的优化流程(本节第四页)。

示例二:双阈值 + 采样,控制日志量 ​

高峰期 1 秒以上语句每分钟数千条时的配置:

conf
# 5 秒以上:全量记录
log_min_duration_statement = 5000
# 500ms 以上:10% 采样
log_min_duration_sample = 500
log_statement_sample_rate = 0.1

# 日志前缀:时间 + PID + 用户@库,用于关联与归因
log_line_prefix = '%m [%p] %q%u@%d '
sql
-- 采样比例生效性抽查:同一模板语句的记录条数应约为执行次数的 10%
SELECT count(*) FROM ... -- 对比业务侧执行计数与日志计数

示例三:按角色差异化阈值 ​

sql
-- OLTP 应用账号:200ms(业务 SLO 是 P99 < 1s,取更早的预警线)
ALTER ROLE app_rw SET log_min_duration_statement = '200ms';

-- 报表账号:15s
ALTER ROLE report_ro SET log_min_duration_statement = '15s';

-- 迁移工具账号:干脆关闭,避免批量导入刷屏
ALTER ROLE migrator SET log_min_duration_statement = '-1';

-- 验证
SELECT rolname, rolconfig FROM pg_roles WHERE rolconfig IS NOT NULL;
text
  rolname  |                    rolconfig
-----------+---------------------------------------------------
 app_rw    | {log_min_duration_statement=200ms}
 report_ro | {log_min_duration_statement=15s}
 migrator  | {log_min_duration_statement=-1}
(3 rows)

示例四:jsonlog 格式下解析慢查询 ​

生产环境推荐 jsonlog(PG15+),字段结构化后解析极简:

conf
log_destination = 'jsonlog'
logging_collector = on
text
{"timestamp":"2026-10-07 10:31:02.114 CST","user":"app_rw","dbname":"shopdb",
 "pid":31980,"error_severity":"LOG","message":"duration: 1502.318 ms",
 "statement":"SELECT pg_sleep(1.5);","query_id":0,...}
bash
# 用 jq 一行提取今天所有慢查询的耗时与语句前 120 字符
jq -r 'select(.message | startswith("duration:"))
       | .message + " | " + (.statement // "" | .[0:120])' \
  /var/lib/postgresql/log/postgresql-2026-10-07_*.json | sort -rn | head

更完整的日志解析与统计(含 pgbader 等工具)见《日志轮转与归档》与《CSV 日志与解析》两页。

示例五:一次性全量诊断窗口 ​

业务方反馈「系统偶发卡顿」但说不出何时何地,标准做法是开一个限时全量诊断窗口,用完即关:

sql
-- 1. 开窗:全量记录所有语句(仅限诊断窗口!)
ALTER SYSTEM SET log_min_duration_statement = 0;
SELECT pg_reload_conf();

-- 2. 记录开窗时间,等业务复现 10~30 分钟
SELECT now() AS window_start;
bash
# 3. 关窗
psql -U postgres -c "ALTER SYSTEM SET log_min_duration_statement = '1s';"
psql -U postgres -c "SELECT pg_reload_conf();"

# 4. 对窗口期日志做 pgbadger 分析
pgbadger -f jsonlog --begin '2026-10-07 14:00' --end '2026-10-07 14:30' \
  /var/lib/postgresql/log/postgresql-2026-10-07_*.json -o diag-1400-1430.html

窗口期配置要点:同时把 log_transaction_sample_rate = 1.0 打开可以还原事务序列(日志量会数倍增长,业务低峰操作);窗口结束后除了收回阈值,还应检查磁盘余量,防止日志把分区写满。这种「全量开窗」的 runbook 建议写进团队文档,避免临场手忙脚乱。

注意事项 ​

阈值设为 0 等于全量审计,不要在写密集生产库长期开启。 官方文档明确提示短查询多时开销显著,且日志量会立即失控。全量记录只在短时间诊断窗口内使用,用完立刻改回。

duration 包含锁等待,别急着下「计划坏了」的结论。 一条慢 UPDATE 的第一嫌疑人是行锁竞争,而不是索引缺失。先查 pg_locks / pg_stat_activity(见《pg_locks 锁信息》与《pg_stat_activity 活动会话》两页),再进执行计划分析。

ALTER ROLE ... SET 只对新连接生效。 应用普遍使用长连接与连接池,角色级阈值改完后老连接依然沿用旧值。要么等连接池自然轮换,要么有计划地终止老连接,要么在变更窗口内做。

日志里的语句文本可能包含敏感数据。 即使 PG13+ 有 log_parameter_max_length,语句本身(如 WHERE phone = '138xxxx' 字面量写法)也可能携带敏感信息。日志文件的权限(log_file_mode 默认 0600)、归档存储位置、保留周期都要按含敏数据处理,这是安全合规要求。

采样日志不能用于「漏报」敏感的合规场景。 log_min_duration_sample 是随机采样,不保证记录每一条超阈值语句。合规审计要求全量记录时,只能用 log_min_duration_statement。

log_statement = all 不是慢查询工具。 官方说明指出它与 log_min_duration_statement 同用时语句文本不会重复出现在 duration 行中,且它记录的是全部语句、日志量与开销远大于按耗时记录。看到别人用 log_statement = all 排查慢问题,应当劝阻。

改完参数记得核对 source 与 pending_restart。 有些团队改了 postgresql.conf 却忘了 pg_reload_conf(),或者被 ALTER SYSTEM 的旧值覆盖(postgresql.auto.conf 优先级高于 postgresql.conf)。用 pg_settings.source 列确认当前值来自哪一层。

注意与连接池(PgBouncer transaction 模式)的交互。 事务池化下同一后端会话被多个客户端复用,ALTER ROLE ... SET 的覆盖依然按后端会话生效,但「会话级 SET」会把污染留给下一个复用者——禁止应用代码随意 SET 服务器参数,这类约定在事务池环境下尤其重要。

采样参数与全量参数的阈值关系要写清注释。 log_min_duration_statement 优先于 log_min_duration_sample,如果两个值配置颠倒(全量阈值低于采样阈值),采样参数实际上完全不工作,而多数人不会发现。配置文件里用注释注明「全量阈值必须高于采样阈值」,并在配置巡检时核对。

最佳实践 ​

  1. 把慢查询阈值写进 runbook 并与业务 SLO 对齐。 推荐基线:OLTP 核心库 200~500ms,一般业务库 1s,分析/报表库 5~10s。阈值是告警策略的一部分,不是 DBA 的个人口味。
  2. 先日志、后扩展:用 log_min_duration_statement 作为慢查询治理第一步。 它零依赖、随内核发行、reload 即生效,适合任何环境(包括无法装扩展的云托管库)。pg_stat_statements 与 auto_explain 作为第二、第三层手段接续。
  3. log_line_prefix 必须包含时间、PID、用户、库。 这是官方建议的日志关联方式,也是后续一切解析工具(pgbadger 等)正确工作前提。推荐 '%m [%p] %q%u@%d '。
  4. 用双阈值组合应对高并发:严重慢查询全量、中等慢查询采样。 log_min_duration_statement(如 5s)+ log_min_duration_sample(如 500ms)+ log_statement_sample_rate(如 0.05),既不漏重大异常,又不被日志量压垮。
  5. PG13+ 显式设置 log_parameter_max_length。 默认 -1 会把绑定参数完整写入日志,既可能撑大日志又可能泄露数据。按敏感程度设为 1024 或更小;错误消息侧保持 log_parameter_max_length_on_error = 0。
  6. 按角色差异化阈值,而不是全局一刀切。 应用账号从严、报表账号从宽、迁移/批量工具账号关闭。ALTER ROLE ... SET log_min_duration_statement 一行命令即可,同时把它纳入账号开通模板。
  7. 日志文件按含敏数据管理。 log_file_mode 保持 0600,日志目录不与数据目录混用同一块盘,轮转与保留周期(log_rotation_size / log_rotation_age)纳入容量巡检。
  8. 每周固定解析一次慢查询日志,形成趋势报表。 用 pgbader 或自研脚本统计「按语句模板聚合的次数与总耗时 Top 10」,对比上周。慢查询治理的成效必须用趋势证明,而不是靠单次灭火。
  9. 发现慢查询后按固定动作推进:关联锁与活动会话 → EXPLAIN 复现 → pg_stat_statements 确认是否常态。 单条日志只是起点,完整的六步流程见本节第四页《慢查询优化流程》。
  10. 配合 pg_stat_statements.track 建立语句统计基线。 确认扩展已加载(shared_preload_libraries 含 pg_stat_statements),让日志证据与累计统计可以互相印证。字段级详解见《pg_stat_statements 语句统计》一页。
  11. 重大变更(版本升级、数据迁移)前后各留一份慢查询基线。 升级后对比基线能最快发现计划回归(regression),这是升级 runbook 的固定验收项。
  12. jsonlog 优先于正则解析 stderr 日志。 PG15+ 环境直接切 log_destination = 'jsonlog',采集、过滤、关联的成本都显著下降,详见《JSON 日志(PostgreSQL 15+)》一页。

下一节

继续学习:auto_explain 自动执行计划

参考链接 ​