WAL Writer(预写日志写入进程)
WAL Writer(预写日志写入进程,简称 walwriter)是 PostgreSQL 的常驻辅助进程,负责把共享内存中的 WAL 缓冲区(WAL buffers)周期性写入磁盘,从而减少后端进程在提交事务时必须自己刷 WAL 的次数。本节将讲解 WAL 写入的两阶段模型、组提交(Group Commit)机制、walwriter 与后端提交刷盘的分工,以及相关参数的实测调优方法。
前置知识
阅读本节前,建议先了解:Background Writer(后台写入进程)
基础概念
| 术语 | 英文 | 说明 |
|---|---|---|
| 预写日志 | WAL, Write-Ahead Logging | 「先写日志、再写数据文件」的持久化机制,保证崩溃后可重放恢复 |
| WAL 缓冲区 | WAL Buffers | 共享内存中暂存 WAL 记录的区域,由 wal_buffers 指定大小 |
| WAL 写入进程 | WAL Writer(walwriter) | 常驻辅助进程,周期把 WAL 缓冲区 write() 到 WAL 文件 |
| 日志序列号 | LSN, Log Sequence Number | WAL 中的字节位置,单调递增,用于描述持久化进度 |
| 组提交 | Group Commit | 多个事务的提交记录合并后一次性刷盘,摊薄 fsync 成本 |
| 同步提交 | Synchronous Commit | synchronous_commit 控制提交时是否等待 WAL 落盘确认 |
| 刷写 | Write | 数据从用户态内存进入内核页缓存(write() 系统调用) |
| 落盘 | Flush / Sync | 内核把页缓存真正写到持久介质(fsync() / fdatasync()) |
| 写入位置 | pg_current_wal_insert_lsn() | WAL 缓冲区中已插入记录的末端 |
| 刷盘位置 | pg_current_wal_flush_lsn() | 已保证落盘的 WAL 末端,提交可见性的判据 |
常见误区:write ≠ flush
这是理解 WAL 写入性能的核心。write() 只把数据交给操作系统页缓存,此时进程即可返回;fsync() 才真正等待数据落到持久介质。二者耗时相差好几个数量级(write 通常是微秒级,机械盘 fsync 是毫秒级)。
walwriter 只做 write(把 WAL 缓冲区搬到内核),它不做 fsync。真正保证持久性的 fsync 由「事务提交者」(后端进程或 walsender)或 walwriter 在必要时触发。
这个分工的意义在于:walwriter 提前把 WAL 推进到内核页缓存,后端提交时往往只需一次 fsync(甚至因为别人已经 fsync 过而完全不用等),从而缩短提交延迟。
WAL 写入的两阶段模型
后端进程执行 UPDATE/COMMIT
│
│ ① 生成 WAL 记录,追加到共享内存的 WAL 缓冲区
▼
┌─────────────────────────────────────────────┐
│ WAL Buffers(wal_buffers) │
│ ┌───────────────────────────────────────┐ │
│ │ record1 | record2 | record3 | ... │ │
│ └───────────────────────────────────────┘ │
└───────────────────┬─────────────────────────┘
│
② write() │ ◀── walwriter 周期性执行(wal_writer_delay)
│ ◀── 后端提交时也执行(同步提交路径)
▼
┌─────────────────────────────────────────────┐
│ OS Page Cache(内核页缓存) │
│ (断电会丢,但进程崩溃不会丢) │
└───────────────────┬─────────────────────────┘
│
③ fsync() │ ◀── 后端提交时必须执行(synchronous_commit = on)
│ ◀── 或由 walwriter 的 flush 逻辑推动
▼
┌─────────────────────────────────────────────┐
│ 持久化介质(磁盘 / SSD) │
└─────────────────────────────────────────────┘对应到 LSN,可以这样理解三种「进度」:
pg_current_wal_insert_lsn() — 已写入 WAL 缓冲区的末端(最新)
pg_current_wal_lsn() — 同上(历史别名,等价于 insert)
pg_current_wal_flush_lsn() — 已 fsync 落盘的末端(最保守)-- 观察三种 LSN 的关系:flush_lsn 通常落后于 insert_lsn
SELECT
pg_current_wal_insert_lsn() AS insert_lsn,
pg_current_wal_lsn() AS write_lsn,
pg_current_wal_flush_lsn() AS flush_lsn,
pg_wal_lsn_diff(pg_current_wal_insert_lsn(), pg_current_wal_flush_lsn()) AS lag_bytes; insert_lsn | write_lsn | flush_lsn | lag_bytes
-------------+------------+------------+-----------
0/1A3E2B70 | 0/1A3E2B70 | 0/1A3DFF08 | 0在高并发提交场景下,lag_bytes 会频繁抖动,那是正常现象;它的稳定值反映的是「WAL 缓冲区到磁盘」的时延。
核心机制
walwriter 的工作循环
walwriter 的逻辑比 bgwriter 简单得多——它不挑页,只负责「把已有的 WAL 推进下去」:
/* 概念性伪代码,非 PostgreSQL 源码原文 */
for (;;)
{
/* 1. 按 wal_writer_delay 休眠(默认 200ms) */
pg_usleep(wal_writer_delay);
/* 2. 取当前写入位置(insert LSN) */
cur = GetXLogInsertRecPtr();
/* 3. 把 [上次已写位置, cur) 区间 write() 到 WAL 文件 */
WriteXLog(cur);
/* 4. 记录本轮的 write 位置,供后端提交时判断「是否已在内核中」 */
LogwrtResult.Write = cur;
/* 5. 若距上次 flush 超过 wal_writer_flush_after 字节,或该值为 0,
则额外执行 fsync,把 WAL 真正推到磁盘 */
if (wal_writer_flush_after == 0 ||
bytes_written_since_flush >= wal_writer_flush_after)
FlushXLog(cur);
}关键点在第 4、5 步:
- 第 4 步:walwriter 记录下「我已经 write 到哪个位置」。后端进程提交时,只要发现目标 LSN 已经在这个位置之前,就知道不需要再做 write(数据已在内核中),可以直接进入 fsync 或直接返回。
- 第 5 步:只有在
wal_writer_flush_after = 0(表示「每次都 flush」)或累计写入超过该字节数时,walwriter 才做 fsync。默认值1MB意味着 walwriter 通常只 write 不 flush,把 fsync 的「责任」留给提交者。
组提交(Group Commit):让 fsync 被摊薄
fsync 是提交路径上最昂贵的操作。如果每个事务都独立 fsync,机械盘上 QPS 会被限制在几百(每次 fsync 约 5~10ms)。PostgreSQL 用组提交把多个事务的 fsync 合并:
时刻 T0:事务 A 完成,进入提交,发现 WAL 未刷盘 → 开始 fsync,持有 WAL 插入/刷盘锁
时刻 T0+ε:事务 B、C、D 也完成,想提交
发现 A 正在 fsync,于是它们「挂起等待」,把自己的目标 LSN 记下来
时刻 T0+5ms:A 的 fsync 完成,把 WAL 推进到 LSN_X
→ B、C、D 的目标 LSN 都 ≤ LSN_X,全部立即返回「提交成功」
→ 4 个事务只花了 1 次 fsync这个过程是自动发生的,不需要配置。当并发提交数较高时,组提交会自动把 fsync 次数压到接近「每 5ms 一次」,这正是 PostgreSQL 能在普通机械盘上跑出上万 TPS 的原因。
可视化理解:fsync 的耗时(灰色)被多个事务共享,事务数越多,单位事务摊销的 fsync 成本越低。
事务数少(无组提交效果) 事务数多(组提交生效)
A ████████ A ████████
B ████████ B ┘ 共享一次
C ████████ C ┘ fsync
D ████████ D ┘
时间 ──────────────────▶ 时间 ──▶commit_delay 与 commit_siblings 是「主动等待以凑组」
组提交默认是「谁先到谁发起,后来者搭便车」。如果你希望主动多等一会儿以凑更大的组,可以设置:
commit_delay(微秒,默认 0):发起 fsync 前先短暂睡眠,给其他事务「赶到」的机会。commit_siblings(默认 5):只有当当前活跃事务数 ≥ 该值时,commit_delay才生效。
绝大多数场景不需要设置这两个参数。只有当你在极高并发、且确认 fsync 是瓶颈、同时 CPU 有空闲时才值得尝试。设错了会平白增加每次提交的延迟。
后端进程在提交路径上的动作
walwriter 只是「辅助加速」,真正的正确性保证在提交路径上。后端进程在提交时依次做三件事:
COMMIT 到达
│
├─ 1. 生成 commit WAL 记录,追加到 WAL 缓冲区
│
├─ 2. 检查 synchronous_commit 设置:
│ off / local → 不等待刷盘,立即返回「提交成功」(异步提交,崩溃可能丢最近事务)
│ on / remote_write / remote_apply → 需要等待
│
├─ 3. 若需要等待:
│ WalWriter 是否已经 write 过这个位置?
│ 是 → 跳过 write,直接进 fsync 判断
│ 否 → 自己执行 write
│ 是否已经被 flush 过(flush_lsn ≥ 目标)?
│ 是 → 直接返回(可能完全是别人的 fsync 帮了忙)
│ 否 → 自己执行 fsync(此时若已有他人正在 fsync,则等待并搭便车)
│
└─ 4. 返回提交成功给客户端注意第 3 步:后端提交时的 write 和 fsync 都可能被 walwriter 或并发事务「提前完成」。这就是 walwriter 的价值——它把 WAL 提前推进到内核,使得大量提交只需等待(或复用)一次 fsync。
-- 当前会话的同步提交模式
SHOW synchronous_commit;
SHOW wal_writer_delay;
SHOW wal_writer_flush_after;
SHOW wal_buffers; synchronous_commit
--------------------
on
wal_writer_delay
------------------
200ms
wal_writer_flush_after
------------------------
1MB
wal_buffers
------------
512kB关键参数
| 参数 | 默认值 | 作用 |
|---|---|---|
wal_buffers | -1(自动,约为 shared_buffers 的 1/32,上限 16MB) | WAL 缓冲区大小;-1 表示自动计算 |
wal_writer_delay | 200ms | walwriter 每轮之间休眠多久 |
wal_writer_flush_after | 1MB | 累计写入多少字节后 walwriter 才做 fsync;0 表示每轮都 flush |
synchronous_commit | on | 提交时等待 WAL 落盘的程度 |
wal_sync_method | 平台相关(Linux 通常 fdatasync) | 用哪个系统调用落盘 |
commit_delay | 0 | 主动延迟以凑组提交(微秒) |
commit_siblings | 5 | commit_delay 生效所需的并发事务数下限 |
full_page_writes | on | 检查点后首次修改页时写整页镜像(属于 WAL 内容层,见后续章节) |
synchronous_commit = off 的取舍
设为 off(或 local)会让提交不等 WAL 落盘就返回成功,吞吐可提升数倍(尤其在机械盘上),代价是崩溃时可能丢失最近若干毫秒(通常 < 200ms)已提交的事务,并且可能造成「已提交数据消失」的语义破坏。
适用场景:日志、埋点、缓存回填等能容忍少量丢失的写入。不适合任何有账务、订单、状态机语义的业务。
注意 off 丢的是「最近一小段时间」的事务,而不是「整库不一致」——数据库自身仍然是一致的(因为 WAL 保证原子性),只是最近提交的事务可能整体消失。
详细说明
walwriter 只 write 不 flush 的设计意图
为什么默认 wal_writer_flush_after = 1MB,而不是让 walwriter 每次都 fsync?
若 walwriter 每 200ms 就 fsync 一次(即 flush_after = 0):
- 好处:后端提交时很可能发现「已 flush」,直接返回,延迟极低
- 坏处:每秒固定 5 次 fsync,若写入量很小,这些 fsync 是纯浪费
(而且长跑的后台 fsync 会与前台 fsync 争抢 I/O)
若默认每 1MB 才 flush:
- 好处:写入量少时不产生无谓 fsync;写入量大时 flush 频率自然升高
- 坏处:低并发提交场景下,后端仍需自己 fsync(此时组提交帮不上忙)结论:walwriter 的 write 是几乎零成本的高收益动作(把数据从共享内存搬到内核),因此它周期性地做;而 flush 是有成本的动作,因此只在明确的量阈值或显式配置下做。
高并发提交场景可以尝试 wal_writer_flush_after = 0
如果你的负载是「大量小事务持续提交」,且观察发现后端提交仍然频繁自己 fsync,把 wal_writer_flush_after 设为 0 让 walwriter 每轮(200ms)主动 flush 一次,往往能显著降低提交延迟的 P99。原因是此时前端几乎总能「搭上便车」,不必自己触发 fsync。
反之,如果是「写入稀疏」的负载(如每分钟几笔),保持默认即可。
用 pg_stat_io 观测 WAL 的 write 与 fsync(PG 16+)
PG 16 引入的 pg_stat_io 视图把 WAL 的写入与同步拆开统计:
-- PG 16+:查看 WAL 文件的写入与 fsync 计数
SELECT
backend_type,
object,
writes,
write_bytes,
fsyncs,
fsync_time
FROM pg_stat_io
WHERE object = 'wal'
ORDER BY writes DESC; backend_type | object | writes | write_bytes | fsyncs | fsync_time
-----------------+--------+---------+-------------+--------+------------
client backend | wal | 892341 | 1843201920 | 12874 | 1842190
walwriter | wal | 742113 | 1520334120 | 41 | 6120
walsender | wal | 18220 | 38881204 | 812 | 102330
checkpointer | wal | 1201 | 2482190 | 302 | 90210关键解读:
walwriter的fsyncs极少(41 次):印证了「默认只 write 不 flush」。client backend的fsyncs最多(12874 次):这是提交路径上的 fsync,是持久性的真实成本所在。walwriter的writes次数高但fsyncs低:说明它有效分担了 write 工作,让后端提交时可以跳过 write 直接 fsync。
要评估 walwriter 的效果,可以对比 walwriter 的 write_bytes 占总 WAL write_bytes 的比例:
-- PG 16+:walwriter 承担了多少比例的 WAL 写入量
SELECT
sum(write_bytes) FILTER (WHERE object = 'wal') AS total_wal_write,
sum(write_bytes) FILTER (WHERE object = 'wal' AND backend_type = 'walwriter') AS by_walwriter,
round(100.0 * sum(write_bytes) FILTER (WHERE object = 'wal' AND backend_type = 'walwriter')
/ NULLIF(sum(write_bytes) FILTER (WHERE object = 'wal'), 0), 2) AS pct_by_walwriter
FROM pg_stat_io; total_wal_write | by_walwriter | pct_by_walwriter
-----------------+--------------+------------------
3427199734 | 1520334120 | 44.36pct_by_walwriter 在 30%~60% 区间属于常见健康值。若它接近 0,说明 walwriter 基本没起作用(可能是 wal_writer_delay 过大或写入过于稀疏)。
walwriter 与 WAL 缓冲区大小的关系
wal_buffers 决定 WAL 记录能暂存多少。它的自动值规则是:
wal_buffers = -1(默认)时:
自动取值 ≈ shared_buffers / 32
下限:64kB(在极小 shared_buffers 时,至少要能容下几个页的 WAL)
上限:16MB(即使 shared_buffers 很大也不会超过)
例:
shared_buffers = 128MB → wal_buffers ≈ 4MB
shared_buffers = 8GB → wal_buffers = 16MB(触顶)-- 查看当前的自动值与来源
SELECT name, setting, unit, source, pending_restart
FROM pg_settings
WHERE name = 'wal_buffers'; name | setting | unit | source | pending_restart
-------------+---------+------+---------+-----------------
wal_buffers | 4096 | 8kB | default | f4096 × 8kB = 32MB——咦,超过上限了?这是显示上的换算陷阱:
pg_settings.setting 的单位是 8kB 页,不是字节
wal_buffers = 4096 表示 4096 个 8kB 页,即 32MB。但手册规定上限为 16MB。实际观察时以 SHOW wal_buffers 的输出为准(它会显示成 16MB),且需注意 unit 列的含义。
准确做法:
SELECT pg_size_pretty(current_setting('wal_buffers')::bigint) AS wal_buffers_human;-- 更可靠的读法:用 current_setting 转成人类可读
SELECT current_setting('wal_buffers') AS raw, pg_size_pretty(current_setting('wal_buffers')::bigint);什么时候需要手动调大 wal_buffers?
- 单个事务产生大量 WAL(如一次
COPY或大批量INSERT)时,WAL 缓冲区容易被填满,后端会在此处同步等待刷盘(产生WALWriteLock等等待事件)。 - 观察
pg_stat_activity中wait_event = 'WALWriteLock'或'WALBufferFull'的会话数。
不过一般来说,自动值已经够用,手动调大的收益有限;更好的手段是拆批提交(把大事务拆成多个小事务)。
提交延迟的诊断链路
如果应用反馈「提交慢」,按下面的顺序排查:
1. 确认是否真的是 fsync 慢
→ 开启 track_io_timing,然后看 pg_stat_io 中 wal 的 fsync_time / fsyncs 平均值
→ 或对比 wal_sync_method 改为 open_datasync / fdatasync 的差异
2. 确认组提交是否生效
→ 看应用并发度:串行提交(一次只有一个事务在飞)时组提交完全无效
→ 看 client backend 的 fsyncs 是否 ≈ 提交次数(若是,则完全没搭上便车)
3. 确认 walwriter 是否在帮忙
→ 看 pct_by_walwriter 是否过低
→ 若过低,尝试 wal_writer_delay 缩短 或 wal_writer_flush_after = 0
4. 确认 synchronous_commit 是否被误设为 on(在不必要的场景)
→ 非关键业务可评估 off / local
5. 确认存储本身
→ 云盘 IOPS 限制、RAID 卡写缓存未开(无 BBU 时写穿透)、
SSD 寿命末期降速、文件系统挂载选项(barrier 等)实战示例
示例 1:测量一次提交的真实 WAL 刷盘延迟
-- 需要 track_io_timing = on 才有耗时数据
SHOW track_io_timing; track_io_timing
-----------------
on-- 记录基线
SELECT sum(fsyncs) AS total_fsyncs, sum(fsync_time) AS total_fsync_time
FROM pg_stat_io
WHERE object = 'wal';-- 生成 500 次独立提交(每次一个事务,模拟小事务高并发)
DO $$
BEGIN
FOR i IN 1..500 LOOP
INSERT INTO t_wal_commit_demo (note) VALUES ('c' || i);
END LOOP;
END $$;-- 对照增量
SELECT
sum(fsyncs) FILTER (WHERE object = 'wal') AS wal_fsyncs,
sum(fsync_time) FILTER (WHERE object = 'wal') AS wal_fsync_time_us,
round(sum(fsync_time) FILTER (WHERE object = 'wal')::numeric
/ NULLIF(sum(fsyncs) FILTER (WHERE object = 'wal'), 0), 1) AS avg_us_per_fsync
FROM pg_stat_io; wal_fsyncs | wal_fsync_time_us | avg_us_per_fsync
------------+-------------------+------------------
502 | 2482190 | 4945.8解读:wal_fsyncs ≈ 502(约等于提交次数),说明这次是串行提交、组提交完全没生效,每次提交都实打实付了约 4.9ms 的 fsync 成本。这也解释了为什么「循环里逐条 INSERT 且每条自动提交」的性能极差。
-- 对比:把这 500 条放进一个事务里,只付 1 次 fsync
BEGIN;
INSERT INTO t_wal_commit_demo (note)
SELECT 'b' || g FROM generate_series(1, 500) AS g;
COMMIT;SELECT
sum(fsyncs) FILTER (WHERE object = 'wal') AS wal_fsyncs_after
FROM pg_stat_io; wal_fsyncs_after
------------------
503增量只有 1。这就是「批量提交」与「逐条提交」的性能差距来源。
示例 2:用 LSN 观察 walwriter 的推进节奏
-- 建一个辅助观察表,记录 insert_lsn 与 flush_lsn 的差距随时间的变化
CREATE TABLE IF NOT EXISTS t_wal_lsn_sample (
ts timestamptz DEFAULT clock_timestamp(),
insert_lsn pg_lsn,
flush_lsn pg_lsn,
lag_bytes bigint
);
-- 采样 10 秒,每 200ms 一次,观察 lag 波动
DO $$
BEGIN
FOR i IN 1..50 LOOP
INSERT INTO t_wal_lsn_sample (insert_lsn, flush_lsn, lag_bytes)
SELECT
pg_current_wal_insert_lsn(),
pg_current_wal_flush_lsn(),
pg_wal_lsn_diff(pg_current_wal_insert_lsn(), pg_current_wal_flush_lsn());
PERFORM pg_sleep(0.2);
END LOOP;
END $$;-- 查看 lag 的分布:最大值反映最坏情况下的刷盘延迟
SELECT
count(*) AS samples,
round(avg(lag_bytes)) AS avg_lag_bytes,
max(lag_bytes) AS max_lag_bytes
FROM t_wal_lsn_sample; samples | avg_lag_bytes | max_lag_bytes
---------+---------------+---------------
50 | 0 | 0(注:空闲实例上 lag 通常恒为 0;只有在持续写入时才会看到非零值)用 wal_writer_delay 做对照实验
在持续写入的负载下,把 wal_writer_delay 从 200ms 调到 20ms,重新采样 max_lag_bytes,通常会看到明显下降(因为 walwriter 推进更频繁)。代价是 walwriter 进程唤醒更频繁、占用更多 CPU。这就是「延迟」与「开销」的典型权衡。
示例 3:评估 synchronous_commit 的影响
-- 逐条提交 + synchronous_commit = on
SET synchronous_commit = on;
\timing on
DO $$
BEGIN
FOR i IN 1..200 LOOP
INSERT INTO t_wal_commit_demo (note) VALUES ('on-' || i);
END LOOP;
END $$;Time: 1182.430 ms-- 逐条提交 + synchronous_commit = off
SET synchronous_commit = off;
DO $$
BEGIN
FOR i IN 1..200 LOOP
INSERT INTO t_wal_commit_demo (note) VALUES ('off-' || i);
END LOOP;
END $$;Time: 96.215 ms差距约 12 倍。这直观地说明:fsync 才是提交路径的瓶颈,而 synchronous_commit = off 本质上是把 fsync 从提交路径上「摘掉」。
但请记住代价:
synchronous_commit = off 时,若数据库进程崩溃(崩溃恢复)——
已提交但未落盘的事务会丢失
若发生操作系统崩溃 / 断电——
丢失更多(还包括内核页缓存中未落盘的部分)
若仅客户端进程崩溃 / 连接断开——
不丢数据(因为事务已在数据库内存中完成)示例 4:对比 wal_writer_flush_after 的两种取值
# postgresql.conf —— 方案 A:默认(只 write,让提交者 fsync)
wal_writer_delay = 200ms
wal_writer_flush_after = 1MB# postgresql.conf —— 方案 B:每轮都 flush(高并发小事务场景可尝试)
wal_writer_delay = 200ms
wal_writer_flush_after = 0 # 0 表示 walwriter 每轮都执行 fsync# 施加负载并对比 P99 提交延迟(需基准测试工具,如 pgbench)
pgbench -c 32 -j 4 -T 60 -f insert_only.sql postgres# 方案 A 典型输出
latency average = 1.842 ms
latency stddev = 2.113 ms
tps = 17021.32 (without initial connection time)
# 方案 B 典型输出
latency average = 1.511 ms
latency stddev = 1.204 ms
tps = 19284.07 (without initial connection time)方案 B 的平均延迟下降、标准差显著降低,这是因为它让前端提交总能搭上 walwriter 的便车,减少了「恰好没人 fsync 就得自己来」的长尾。但请注意:这是在高并发前提下的收益;低并发时方案 B 只是多做了无用的 fsync。
注意事项
- walwriter 不影响正确性:即使它完全不工作,后端提交路径也会自己完成 write + fsync,数据不会丢。
wal_writer_delay与bgwriter_delay都受 SIGHUP 影响:改配置后需reload,SET命令无法改变已运行进程的行为。synchronous_commit = off不等于「不写 WAL」:WAL 仍然照写,只是提交不等 fsync。崩溃恢复仍然可用。synchronous_commit是会话级参数:可以用SET在特定事务/会话上临时放宽(如批量导入时),不必全局关闭。commit_delay是微秒不是毫秒:设成1000是 1ms,不是 1 秒;设错会造成严重性能问题。wal_buffers手动调大需要重启:该参数是postmaster级,必须重启实例。- 组提交需要并发才会生效:单连接串行提交时,无论怎么调参都不会有组提交收益,此时应改用批量提交。
- fsync 慢不总是数据库的问题:云盘 IOPS 限额、文件系统 barrier、RAID 写缓存策略、虚拟化层的
fsync语义都可能放大延迟。 pg_stat_io需要 PG 16+:更早版本只能通过pg_stat_bgwriter与pg_stat_wal(PG 14+)间接推断。pg_stat_wal(PG 14+)是查看 WAL 生成量的专用视图:wal_records、wal_fpi(全页镜像数)、wal_bytes都在这里,与pg_stat_io互补。
# postgresql.conf —— 不推荐:为提速把持久性全局关掉
# synchronous_commit = off # 不推荐:全局关闭会让所有业务都面临丢最近事务的风险
# full_page_writes = off # 不推荐:崩溃后可能无法恢复(见 WAL 全页写入章节)-- 不推荐:无脑调大 commit_delay 期望提升吞吐
-- SET commit_delay = 10000; -- 不推荐:10ms,会直接抬高每次提交的延迟最佳实践
- 保持 walwriter 与组提交在默认配置:默认值在绝大多数场景下已是最优或接近最优。
synchronous_commit只在会话级放宽:批量导入、日志类写入可在会话内SET synchronous_commit = off,不要全局关闭。- 优先用批量提交替代调参:把 N 条逐条提交改成 1 个事务或分批提交,收益远大于调
wal_writer_*。 - 高并发小事务场景可试
wal_writer_flush_after = 0:务必用压测对比 P99 后再决定。 - 用
pg_stat_io的object = 'wal'监控 WAL 的 write/fsync 拆分,不要只看总 I/O。 - 开启
track_io_timing仅用于诊断期:长期开启有可测量的开销。 - 把存储的 fsync 能力纳入选型:能保证断电不丢缓存的企业级 SSD 或带 BBU 的 RAID 卡,能把提交延迟压下一个数量级。
- 监控
pg_stat_wal.wal_bytes的增长率:它反映写入压力,也直接影响检查点触发频率(max_wal_size)。 - 不要用
synchronous_commit = off掩盖存储问题:如果 fsync 异常慢,先查存储,而不是关掉持久性。 - 调参必须做 A/B 压测:
wal_writer_delay、wal_writer_flush_after、commit_delay的效果高度依赖负载特征,纸上推导不可靠。
下一节
继续学习:Autovacuum(自动清理进程)