适用版本:PostgreSQL 13 ~ 17(标注 15+/17+ 的为高版本特性)
定位:从”参数怎么配”到”日志怎么读、事故怎么查”的完整闭环。
阅读路径:① 日志体系总览 → ② 参数全景与生效级别 → ③ csvlog 字段逐列解析 → ④ SQLSTATE 与日志级别 → ⑤ 生产配置模板 → ⑥ 排查 SOP / Checklist。
1. 日志体系总览
PostgreSQL 的日志分三层,不要混淆:
| 层次 | 作用 | 对应参数/进程 | 类比 Oracle |
|---|---|---|---|
| 日志收集器(logger) | 后台进程,通过 pipe 把 postgres 进程输出到 stderr 的内容捕获、落盘/轮转 | logging_collector、pg logger 进程 | diag 进程写 alert.log |
| 日志目的地(log_destination) | 落盘格式:stderr / csvlog / jsonlog(15+) / syslog / eventlog | log_destination | trace 文件格式 |
| 日志内容开关 | 决定”记录什么”:连接、慢 SQL、锁等待、检查点、临时文件…… | log_* 系列 | audit / sql_trace / 10046 事件 |
1.1 核心机制(为什么 logging_collector 必须 on)
- 后端进程(backend)本身不直接写日志文件,而是把日志写到
stderr。 logging_collector = on时,启动一个专用logger后台进程,通过 匿名 pipe 收集所有 backend 写到 stderr 的内容,再按log_filename规则切片写盘。- 因此:
- 要写文件日志,
logging_collector必须 = on,且它是 postmaster 级参数,改完必须 restart(不是 reload)。 log_directory是相对data_directory的路径(除非写绝对路径)。csvlog不是独立目的地,而是在 stderr 基础上”附加一份 CSV 结构化日志”;log_destination='stderr,csvlog'可同时输出。- 高版本(15+)
jsonlog输出 JSON 行,适合直接进 ES/Loki,无需再解析 CSV。
- 要写文件日志,
1.2 log_destination 对比
| 目的地 | 格式 | 优点 | 缺点 | 生产建议 |
|---|---|---|---|---|
stderr | 纯文本 | 人读最友好 | 无法结构化提取 SQL/状态码 | 容器调试用 |
csvlog | CSV(23 列) | 字段可入库、可 SQL 关联 | 需 filebeat/脚本解析 | OLTP 主力推荐 |
jsonlog(15+) | JSON Lines | 结构化、直接进日志平台 | 旧版不支持 | PG15+ 上日志平台首选 |
syslog | 系统 syslog | 复用 syslog 链路 | 丢上下文、时区重复、字段被 syslog 截断 | 小规模或托管环境 |
eventlog | Windows 事件日志 | Windows 原生 | 不适合 Linux 生产 | 仅 Windows |
生产常见组合:
log_destination = 'csvlog',采集端用 Filebeat/Vector 解析 CSV 后送 Loki/ES/Kafka。
2. 参数全景(按职能分组 + 生效级别)
生效级别含义:
- postmaster:改完必须
restart。 - sighup:改完
SELECT pg_reload_conf();或pg_ctl reload即生效。 - superuser / user:可在会话内
SET。
2.1 输出与轮转(文件形态)
| 参数 | 示例值 | 级别 | 说明 |
|---|---|---|---|
logging_collector | on | postmaster | 日志收集器开关,落盘前提 |
log_destination | 'csvlog' | sighup | 日志目的地 |
log_directory | 'log' | postmaster | 日志目录,相对 data 目录 |
log_filename | 'postgresql-%Y-%m-%d_%H%M%S.log' | sighup | 文件名模板,支持 strftime 占位符 %Y %m %d %H %M %S %a(星期) |
log_file_mode | 0600 | sighup | 日志文件权限,不要用 0666(含 SQL 可能敏感) |
log_rotation_age | 1d | sighup | 按时间切分 |
log_rotation_size | 1GB | sighup | 按大小切分,任一阈值先到先切 |
log_truncate_on_rotation | off | sighup | on 时同名文件覆盖而非追加;配合 %a(按星期)做 7 天覆盖式轮转 |
坑:
log_truncate_on_rotation = on只有在新文件名与旧文件同名时才 truncate;log_filename带秒级%S时永远不同名,truncate 永远不生效。要做”固定保留 N 天覆盖”,用postgresql-%a.log(按星期几命名)。
2.2 日志内容开关(决定记什么)
| 参数 | 推荐值(生产) | 级别 | 说明 |
|---|---|---|---|
log_min_messages | warning | sighup | 服务器端记录的最低级别(DEBUG5/DEBUG4/…/INFO/NOTICE/WARNING/ERROR/LOG/FATAL/PANIC) |
log_min_error_statement | error | sighup | 出错 SQL 本身在≥该级别时记录到 query 列 |
log_min_duration_statement | 300ms~`1s` | sighup/superuser | 慢查询阈值,超过即记录完整 SQL 与时长;0=全部,-1=关闭 |
log_statement | ddl | sighup | none / ddl / mod / all。all 生产禁用(量爆 + 泄露数据) |
log_duration | off | sighup | 记录每条 SQL 耗时;量极大,用 log_min_duration_statement 替代 |
log_checkpoints | on | sighup | 记录 checkpoint / restartpoint(估算 I/O 抖动) |
log_connections | off(高并发) | sighup | 每次连接一条;高并发 OLTP 会刷屏,合规要求才 on |
log_disconnections | off | sighup | 断开会话,可看会话时长 |
log_lock_waits | on | sighup | 锁等待超过 deadlock_timeout 才记录(默认 1s,可调到 200ms 抓短锁) |
deadlock_timeout | 1s | sighup | 死锁检测周期,也是 log_lock_waits 的触发阈值 |
log_temp_files | 64MB | sighup | 临时文件超过该大小即记录(0=全记,-1=关);排查排序/哈希落盘 |
log_autovacuum_min_duration | 100ms | sighup | autovacuum 动作慢于该值才记录 |
log_replication_commands | on | sighup | 记录复制起停命令(审计主从切换) |
log_recovery_conflict_waits | on | sighup | 备库因恢复冲突等待的语句(主从延迟排查关键) |
log_error_verbosity | default | sighup | terse / default / verbose,verbose 会带 internal query/context |
log_executor_stats / log_statement_stats | off | superuser | 统计每 SQL 资源开销,生产开了等于自 DoS,仅排查临时用 |
2.3 日志行前缀 log_line_prefix(完整占位符表)
截图用的前缀:
log_line_prefix = '%m [%p]: [%l] [txid=%x] user=%u,db=%d,app=%a,client=%r '
全部占位符(务必背熟 %c %l %x %i 这几个关联排查用得到的):
| 占位符 | 含义 |
|---|---|
%a | application_name(psql/应用连接串里设置) |
%u | 数据库用户 |
%d | 数据库名 |
%p | 后端进程 PID |
%r | 远端 host:port(本地连接显示 [local]) |
%h | 远端 host(IP;log_hostname=on 才反解主机名,生产保持 off 避免 DNS 延迟) |
%m | 时间戳(带毫秒,本地时区) |
%t | 时间戳(不带毫秒) |
%n | 带毫秒时间戳(无 syslog 自带时间戳场景的变体) |
%c | 会话 ID:backend_start(epoch hex).pid(hex),如 65eae855.443b,串联一个会话全部日志的主键 |
%l | 该会话内日志行序号(从 1 开始),即截图里的 “Line number” |
%i | 当前命令标签(command tag,如 SELECT/INSERT) |
%s | 会话启动时间 |
%v | 虚拟事务 ID(backend 级,未开事务也有) |
%x | 顶层事务 ID;0 表示当前不在事务块中;截图里 commit 事务 ID=746 |
%e | SQLSTATE 错误码(出错时才有值) |
%q | 切换点:其后格式仅在会话 backend 进程中输出(WAL sender/checkpointer 等后台进程用不到时抑制) |
%% | 字面 % |
生产精简推荐前缀:
log_line_prefix = '%m [%p] %c txid=%x user=%u db=%d app=%a client=%r '带
%c是为了能把”某一次慢 SQL → 锁等待 → 连接断开”串成一条时间线。
3. csvlog 日志逐列解析(对照截图 15 个字段补全)
csvlog 实际有 23 列,截图是核心 15 列,完整对照表如下:
| # | 列名 | 截图对应 | 说明/取值示例 |
|---|---|---|---|
| 1 | log_time | ① 时间戳 | 2024-03-08 02:28:42.981 PST |
| 2 | user_name | ② 用户名 | postgres |
| 3 | database_name | ③ 数据库名 | postgres |
| 4 | process_id | ④ 进程 ID | 17467 |
| 5 | connection_from | ⑤ 远端 IP/HOST | [local] 或 10.0.0.5:5433 |
| 6 | session_id | ⑥ 会话 ID | 65eae855.443b,= %c |
| 7 | session_line_num | ⑦ Line number | 会话内第几条日志,从 1 递增 |
| 8 | command_tag | ⑧ 数据库状态 | SELECT / CREATE TABLE 等命令标签 |
| 9 | session_start_time | ⑨ 会话启动时间 | 2024-03-08 02:28:37 PST |
| 10 | virtual_transaction_id | — | 如 3/45 |
| 11 | transaction_id | ⑩ 事务 ID | 746;0 = 非事务中 |
| 12 | error_severity | ⑪ 日志级别 | LOG / STATEMENT / ERROR / FATAL … |
| 13 | sql_state_code | ⑫ 状态码 | 00000 表示无错误 |
| 14 | message | ⑬ 语句文本 | statement: create table t1(id int); |
| 15 | detail | — | 错误详情,如”键值已存在,违反唯一约束” |
| 16 | hint | — | 官方建议 |
| 17 | internal_query | — | 内部触发 SQL(规则/约束) |
| 18 | internal_query_pos | — | 内部 SQL 位置 |
| 19 | context | — | 上下文(如:在 PL/pgSQL 函数 foo 第 15 行) |
| 20 | query | — | 触发错误的用户 SQL(受 log_min_error_statement 控制) |
| 21 | query_pos | — | 出错 SQL 的字符偏移 |
| 22 | location | — | 源码位置(仅 verbose) |
| 23 | application_name | ⑭/⑮ | 客户端/进程类型:psql、client backend、walreceiver… |
3.1 会话 ID(%c / session_id)反查活动会话
截图给的公式可直接用——把日志里的 session_id 拆出来关联 pg_stat_activity:
-- 日志里看到 session_id = '65eae855.443b',反查它当前是否还活着、在干什么
SELECT pid, usename, datname, application_name, client_addr,
state, wait_event_type, wait_event, query, backend_start
FROM pg_stat_activity
WHERE to_hex(trunc(EXTRACT(EPOCH FROM backend_start))::integer) = '65eae855'
AND to_hex(pid) = '443b';
一个会话从 connect 到 disconnect 的所有日志,靠
session_id聚合就是完整时间线;这是事故复盘的核心 join key。
3.2 在数据库里直接读日志(PG15+)
-- 列出日志目录下文件(需 pg_read_server_files 角色)
SELECT * FROM pg_ls_logdir();
-- 读取某日志内容
SELECT * FROM pg_read_logfile('postgresql-2026-11-07_020000.log');
4. 日志级别与 SQLSTATE
4.1 日志级别(从低到高)
DEBUG5 < DEBUG4 < DEBUG3 < DEBUG2 < DEBUG1 <
LOG < INFO < NOTICE < WARNING < ERROR <
LOG(重复) < FATAL < PANIC
client_min_messages:发给客户端的级别;log_min_messages:写日志文件的级别。- 生产
log_min_messages = warning;error及以上必报警。 STATEMENT不是 severity,而是log_statement=all/mod/ddl记录的语句在 message 里以statement: ...出现,级别标记为LOG。
4.2 SQLSTATE 结构(5 字符)
- 前 2 位 = 错误类,后 3 位 = 子类。
- 常见速查(生产值班必背):
| SQLSTATE | 含义 | 处置方向 |
|---|---|---|
00000 | 成功 | 正常语句日志 |
08006 / 08001 | 连接失败/断开 | 网络、pg_hba、连接数打满 |
23505 | 唯一约束冲突 | 业务重复插入 |
23502 | NOT NULL 违反 | 上游数据缺字段 |
23503 | 外键违反 | 主从数据不一致 |
40P01 | 死锁检测 | 查 deadlock 日志的 detail 两个事务互锁语句 |
40001 | 序列化失败 | 隔离级别冲突,重试 |
42P01 | 表不存在 | 拼写/schema 错误 |
42601 | SQL 语法错误 | 代码发布 |
53300 | 超过 max_connections | 连接泄漏,查 idle in transaction |
53100 | 磁盘满 | 立即清 WAL/临时文件 |
57P01 / 57P02 | 管理员关闭/取消 | 重启或 pg_cancel |
XX000 | 内部错误 | 提 bug / 看 core |
5. 生产级配置模板(OLTP 16C/128G 实例参考)
5.1 postgresql.conf 生产推荐段
# ========== 日志收集与文件 ==========
logging_collector = on
log_destination = 'csvlog'
log_directory = 'log'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_file_mode = 0600
log_rotation_age = 1d
log_rotation_size = 1GB
log_truncate_on_rotation = off
# ========== 内容粒度 ==========
log_min_messages = warning
log_min_error_statement = error
log_error_verbosity = default
log_line_prefix = '%m [%p] %c txid=%x user=%u db=%d app=%a client=%r '
# ========== 慢 SQL(核心) ==========
log_min_duration_statement = '300ms' # 按 P95 业务响应调整;门禁高的库用 1s
log_statement = 'ddl' # 审计需求交给 pgaudit,别用 all
log_duration = off
# ========== 锁 / 死锁 ==========
log_lock_waits = on
deadlock_timeout = '200ms' # 默认 1s 太长,抓 200ms 以上等待更敏感
# ========== 运维信号 ==========
log_checkpoints = on
log_connections = off # 高并发保持 off;安全合规 on
log_disconnections = off
log_temp_files = '64MB' # 排序/哈希落盘告警
log_autovacuum_min_duration = '100ms'
log_replication_commands = on
log_recovery_conflict_waits = on
# ========== 不要打开的 ==========
# log_statement_stats / log_executor_stats:谁开谁背锅
5.2 auto_explain(慢 SQL 自动带执行计划,强烈推荐)
# 需先在 shared_preload_libraries 加载:
shared_preload_libraries = 'auto_explain'
auto_explain.log_min_duration = '1s'
auto_explain.log_analyze = on # 真实执行统计
auto_explain.log_buffers = on # 缓冲区命中/读盘
auto_explain.log_nested_statements = on # 函数内嵌套 SQL 也记录
auto_explain.log_timing = on # 高负载采样开销大时改 off
auto_explain.log_triggers = on
效果:超过 1s 的 SQL 自动把
EXPLAIN ANALYZE结果写进日志,不用事后auto_explain复现。
5.3 合规审计:pgaudit
金融/等保场景需要记录全部 DDL/DML 与登录,不要靠 log_statement=all,装 pgaudit:
shared_preload_libraries = 'pgaudit'
pgaudit.log = 'write, ddl, role'
5.4 动态生效方式
-- 改参数后 reload(postmaster 级参数除外)
ALTER SYSTEM SET log_min_duration_statement = '500ms';
SELECT pg_reload_conf();
-- 验证
SHOW log_min_duration_statement;
-- 查哪些参数是 postmaster 级、当前值与来源
SELECT name, setting, sourcefile, pending_restart
FROM pg_settings WHERE name LIKE 'log_%' OR name = 'logging_collector';
6. 生产级实践要点
6.1 日志保留与磁盘治理
pg_log/log目录必须纳入监控:磁盘水位 > 70% 告警,> 85% 自动清理 N 天前日志(cron + find -mtime +7 -delete)。- 保留周期建议:在线 7~14 天;合规要求的审计日志送中心化存储长期留存,本地只留热数据。
- 大日志文件排查:先
ls -lh找最大文件,按session_idgrep 聚合。
6.2 慢日志:日志 vs pg_stat_statements,怎么选
| 维度 | 日志(log_min_duration_statement) | pg_stat_statements |
|---|---|---|
| 形态 | 单次、带参数原文、带时长/计划 | 聚合、带 calls/mean/total_time |
| 用途 | 事故现场、抓单笔慢 SQL | 找 TOP SQL、趋势基线 |
| 建议 | 两者都开,慢查询门禁靠日志,常态优化靠 pg_stat_statements |
6.3 常见”日志坑”清单
logging_collector改了不重启 → 文件根本没生成,以为参数没生效。log_statement=all上生产 → 日志 GB/小时,磁盘打满,触发53100全站故障。log_connections=on在万级 TPS 连接池上 → 日志量压垮 I/O。log_file_mode=0666→ 含敏感数据的 SQL 任何系统用户可读。log_line_prefix不带%c→ 复盘时无法把同一会话的多条日志串起来。log_hostname=on→ 每条连接反解 DNS,引发连接延迟毛刺。log_temp_files=-1忘开 → 排序爆内存落盘却无任何告警。- csvlog 直接用文本编辑器 grep → 引号转义把 SQL 拆成多行,应按 CSV 解析(filebeat 或
COPY ... WITH CSV HEADER入库)。
6.4 日志采集上平台
- Filebeat/Vector 采集
$PGDATA/log/*.csv,用 CSV 解析器拆出 23 列,加 host/cluster/tag 后送 Loki 或 ES。 - 告警规则(PromQL / Loki):
- rate(log{severity=”ERROR”}[5m]) > 5 → 数据库报错风暴
53300(too_many_connections)出现即 P1deadlock_detected(40P01)出现即 P2- 出现
PANIC/XX000→ P0 立即介入
7. 事故排查 SOP(值班 Checklist)
- 确认时间窗:根据故障时间,定位对应切片日志文件。
- 按 severity 过滤:先抓
ERROR / FATAL / PANIC,看 SQLSTATE。 - 按 session_id 聚合:拿到异常会话的完整时间线(connect → SQL → wait → error → disconnect)。
- 关联活动会话:用第 3.1 节 SQL 反查
pg_stat_activity,确认该会话是否还在跑、等什么(wait_event_type/wait_event)。 - 死锁:日志里
deadlock detected段落自带两个事务的 SQL 与持锁关系,按 detail 逐行读。 - 慢 SQL:
log_min_duration_statement记录里找duration: xxx ms开头的行,看是否伴随 auto_explain 的Query Plan。 - 锁等待:
log_lock_waits记录 “process %d still waiting for …”,对比持锁进程。 - 检查点抖动:
checkpoint starting: time/write与checkpoint complete: wrote ... buffers,看 write 耗时是否撞上业务高峰。 - 临时文件暴涨:
log_temp_files记录temporary file: path "base/pgsql_tmp...", size xxx,定位哪个排序/哈希。 - 磁盘/连接:
53100/53300出现时立即查pg_stat_activity的idle in transaction与连接来源 IP。 - 备库:主从延迟先看
log_recovery_conflict_waits与 WAL receiver 日志。
8. 附:与 Oracle 的对照(便于 Oracle DBA 快速映射)
| PostgreSQL | Oracle 对应 |
|---|---|
postgresql-*.csv(logger 进程) | alert_.log |
log_min_duration_statement 慢 SQL | sql_trace / 10046 事件 |
auto_explain | 10053 trace / 执行计划 |
pg_locks + log_lock_waits | UTLLOCKT / 锁阻塞链 |
pg_stat_statements | AWR / v$sqlarea 聚合 |
pgaudit | FGA / audit_trail |
| WAL sender / receiver 日志 | DG 备库 alert.log / LNS/RFS |
| csvlog 23 列 | trace 文件字段的结构化版本 |

徐万新之路

