PostgreSQL 的 fsync:一个设置错误就可能让数据无法恢复

2026-07-24 33 预计阅读时间: 1 分钟
来源: postgr.es AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

fsync 默认开启,看起来只是 postgresql.conf 中一个普通的布尔参数,实际却守着 PostgreSQL 的持久性底线。查询规划参数配错,常见后果是 SQL 变慢;fsync 配错,在断电、操作系统崩溃或宿主机故障后,可能留下无法恢复的数据损坏。

fsync 究竟保证了什么

PostgreSQL 修改数据时,不会让每个事务直接重写完整的数据文件。它先更新共享缓冲区,并把恢复所需的信息写入 WAL。事务提交、检查点和后台写入过程再按照既定顺序,把 WAL 与数据页交给操作系统。

问题在于,操作系统报告一次普通的 write() 成功,通常只代表数据进入了内核页缓存,并不等于数据已经落到非易失存储介质。fsync=on 时,PostgreSQL会在需要建立持久性边界的位置要求操作系统刷新数据,并维持恢复依赖的写入顺序。

如果关闭它,数据库仍然可能正常启动、接受写入并通过日常查询。风险通常要等到突然掉电或系统崩溃才显现:

  • WAL 可能看起来已经提交,但对应数据页的写入状态不符合恢复假设。
  • 数据页、索引页与事务状态可能处于彼此不一致的持久化时点。
  • 重放 WAL 不一定能修复这种写入顺序被破坏后的状态。
  • 备库和备份不能自动挽救尚未可靠落盘、却被当作已提交的数据。

因此,这不是“愿意牺牲一点可靠性换速度”那么简单。关闭 fsync 会改变数据库能够承诺的故障恢复边界。

先检查,不要凭印象判断

可以这样实践:先从正在运行的实例读取实际值、配置来源和参数上下文。将连接串替换为自己的管理连接。

export DATABASE_URL='postgresql://postgres@127.0.0.1:5432/postgres'

psql "$DATABASE_URL" -X -v ON_ERROR_STOP=1 <<'SQL'
SELECT name,
       setting,
       source,
       sourcefile,
       sourceline,
       context,
       pending_restart
FROM pg_settings
WHERE name IN (
  'fsync',
  'synchronous_commit',
  'full_page_writes',
  'wal_sync_method'
)
ORDER BY name;

SHOW data_directory;
SHOW config_file;
SQL

这里不能只看某一份模板中的 postgresql.conf。参数可能来自启动命令、发行版生成的配置、ALTER SYSTEM 写入的 postgresql.auto.conf,或者运维平台注入的设置。sourcesourcefilesourceline 能帮助定位真正生效的来源。

同时不要混淆几个名字相近但边界不同的参数:

  • fsync 控制 PostgreSQL 是否要求内核可靠刷新,并关系到整个实例的崩溃恢复安全。
  • synchronous_commit 影响事务向客户端报告提交成功前等待到什么程度,可按事务调整。
  • full_page_writes 用于降低崩溃期间页面部分写入带来的风险,不是 fsync 的替代品。
  • wal_sync_method 选择具体的 WAL 同步方式,需要结合操作系统和存储测试。

发现 fsync 关闭后,怎样恢复到安全状态

不要只在高流量实例上改成 on,看到配置值变化就宣布完成。关闭期间,操作系统可能仍保存着尚未可靠刷新的脏页;恢复持久性保证时,需要把维护窗口和底层存储一起纳入操作。

可以采用下面的保守流程。具体服务名和 PostgreSQL 二进制目录需按环境修改:

# 1. 确认当前值与配置来源
psql "$DATABASE_URL" -X -Atc \
  "SELECT setting || ' source=' || source || ' file=' || coalesce(sourcefile, '<none>') FROM pg_settings WHERE name='fsync';"

# 2. 写入受 PostgreSQL 管理的配置覆盖
psql "$DATABASE_URL" -X -v ON_ERROR_STOP=1 -c \
  "ALTER SYSTEM SET fsync = 'on';"

# 3. 安排维护窗口,停止数据库,避免继续产生写入
sudo systemctl stop postgresql

# 4. 要求操作系统刷新仍在缓存中的数据
sync

# 5. 启动并复核实际值;服务名可能包含版本或集群名
sudo systemctl start postgresql
psql "$DATABASE_URL" -X -v ON_ERROR_STOP=1 -c \
  "SELECT name, setting, source, pending_restart FROM pg_settings WHERE name='fsync';"

这是一份可改造的通用示例,不是所有部署方式的固定操作手册。Kubernetes、云托管数据库、BSD、macOS 和不同 Linux 发行版的停止方式不同;某些环境也不允许执行 ALTER SYSTEM。应先查看当前 PostgreSQL 版本中 pg_settings.context 的结果,并遵守平台的重载或重启流程。

更重要的是,fsync=on 只能要求操作系统完成持久化,不能让不诚实的存储变诚实。虚拟磁盘、RAID 控制器、消费级 SSD、网络块存储或宿主机缓存如果错误地确认 flush 请求,数据库仍可能丢失数据。应确认写缓存是否有电池或断电保护,并检查云盘对 flush、写入顺序和故障域的承诺。

性能问题不要靠关闭 fsync 解决

如果提交延迟确实集中在同步写入上,可以这样实践:在与生产相同的文件系统和存储路径上使用 PostgreSQL 自带的 pg_test_fsync 比较同步方法。请在独立测试目录中运行,不要把测试文件放进生产数据目录。

mkdir -p /var/tmp/pg-fsync-test
cd /var/tmp/pg-fsync-test
pg_test_fsync

这个工具生成的是低层同步测试结果,不等同于真实业务吞吐量。后续仍应使用代表性事务、连接数和检查点压力做基准测试。更稳妥的优化方向通常包括:

  • 检查 WAL 所在卷的延迟、队列深度和抖动。
  • 合并短事务,避免一次业务操作拆成大量独立提交。
  • 对明确允许丢失最近事务的任务,评估会话级或事务级 synchronous_commit=off,而不是关闭实例级 fsync
  • 调整检查点和 WAL 相关配置前,先采集 pg_stat_bgwriterpg_stat_wal 与存储指标。
  • 通过故障注入或断电测试验证整条存储链路,而不只相信顺序写带宽。

例如,只对可重建的批处理事务放宽提交等待,可以明确限定风险范围:

BEGIN;
SET LOCAL synchronous_commit = off;

-- 仅放置能够从上游重新生成的数据写入
INSERT INTO rebuildable_daily_summary(day, total)
VALUES (CURRENT_DATE, 0)
ON CONFLICT (day) DO UPDATE
SET total = EXCLUDED.total;

COMMIT;

这并不等于事务失去原子性,也不代表它一定会丢失;它表示服务器可能在 WAL 真正持久化前向客户端报告提交成功。系统崩溃时,最近一小段已确认事务可能消失,所以不能用于账务、订单状态或其他无法重建的数据。

上线前的判断清单

fsync=on 视为生产实例的硬性默认值,并在配置审计中持续检查,而不是只在建库时看一次。上线或迁移前至少确认:

  • 运行实例报告 fsync=on,并且配置来源符合预期。
  • 底层存储正确实现 flush 和写入顺序语义。
  • RAID 或设备写缓存具备可靠的断电保护。
  • 性能测试覆盖事务提交延迟、检查点压力和高峰并发。
  • 备份完成过恢复演练,副本也接受过故障切换验证。
  • 任何持久性降级都有明确的数据范围、审批记录和恢复方案。

fsync 的危险之处在于,错误配置往往不会立即报警。数据库可能平稳运行数月,直到一次基础设施故障把隐藏的持久性问题一次性暴露出来。对生产 PostgreSQL,正确做法是保留 fsync=on,再从事务设计、WAL 存储和底层设备中寻找可测量、可控制的性能改进。


相关推荐