Claude Code 改写 PostgreSQL 内核, Full Page Write vs Doublewrite Buffer, 性能差 3 倍
2026 年了, AI 能改数据库内核代码么? 我用 Claude Code 把 PostgreSQL 的 Full Page Write 换成了 MySQL 的 Doublewrite Buffer, 跑出来性能差了 3 倍.
起因
FPW 和 DWB 到底哪个更合理, 这个问题我想了很久. 之前在 pgsql-hackers 的 mailing list 上发过一个帖子讨论这事, 但没讨论出什么结果.
到了 2026 年, 正好想试试 Claude Code 能不能改内核代码, 顺便通过实际代码对比一下两种方案. Show me the code – 于是就有了这次实践.
Torn Page 问题
数据库以页为单位管理数据, PostgreSQL 默认 8KB, MySQL 默认 16KB. 但操作系统和磁盘的原子写入单元一般是 4KB.
这就意味着写一个完整的数据库页到磁盘, 需要多次物理 I/O. 如果写到一半断电了或者系统挂了, 页面就只写了一部分 – Torn Page, 新旧数据混在一起, 页面就坏了.
PostgreSQL 和 MySQL 用了完全不同的办法来解决这个问题.
PostgreSQL: Full Page Write
每次 Checkpoint 之后, 某个数据页第一次被改的时候, PostgreSQL 把整个页的内容写到 WAL 里. 崩溃恢复时从 WAL 拿到完整页面覆盖掉坏的, 再回放后面的 WAL 记录就行了.
但 FPW 有个很要命的问题: 它让 Checkpoint 频率变成了一个两难选择.
FPW 希望少做 Checkpoint – 因为每次 Checkpoint 之后大量页面的首次修改都要写全页, WAL 体积暴涨, 写入性能断崖下跌. PostgreSQL 的 checkpoint_timeout 最低也得 30 秒, 当然了 checkpoint 还有可能是超过了 max_wal_size 的大小来触发.
但数据库理论又希望多做 Checkpoint – Checkpoint 越频繁, crash recovery 要回放的 WAL 越少, 恢复越快.
这俩需求是冲突的: FPW 要少做 Checkpoint, 快速恢复要多做 Checkpoint.
MySQL: Doublewrite Buffer
InnoDB 的思路完全不一样. 刷脏页之前, 先把多个脏页顺序写到磁盘上一块专门的 Doublewrite Buffer 区域. 写满了再做一次 fsync(), 然后才把这些页面离散写到各自的数据文件位置.
crash 了怎么办? 重启时检查 Doublewrite Buffer 里有没有完整的页面副本, 有就拿来修复损坏的数据页. Torn Page 问题就不存在了.
为什么我觉得 Doublewrite Buffer 更合理
前台 vs 后台
不考虑数据合并的话:
- FPW = 1 次 WAL 写入 + 1 次数据页写入
- DWB = 2 次数据页写入
都是 2 次 I/O, 但 WAL 写入是在前台路径上, 直接影响用户的 SQL latency; DWB 的写入在后台刷脏路径上, 用户基本感知不到.
batch 优化空间不同
DWB 不用每次写入都 fsync(), 写满一个 Buffer 才 sync 一次. WAL 写入虽然也能做 batch, 但毕竟是前台路径, 你不能让用户等太久, batch 空间有限.
没有 Checkpoint 频率的矛盾
DWB 不依赖 Checkpoint 来防 Torn Page, 所以你可以随便提高 Checkpoint 频率来加速 crash recovery, 不会有写放大的副作用.
性能测试
主要配置:
shared_buffers=4GB
wal_buffers=64MB
synchronous_commit=on
maintenance_work_mem=2GB
checkpoint_timeout=30s
每个场景重建数据库, 跑之前做一次 VACUUM FULL 加 60 秒预热, 每个负载跑 300 秒.
场景: io-bound, –tables=10 –table_size=10000000
结果如下:

| 场景 | 并发 | FPW OFF (QPS) | FPW ON (QPS) | DWB ON (QPS) | FPW OFF (TPS) | FPW ON (TPS) | DWB ON (TPS) | FPW OFF (ms) | FPW ON (ms) | DWB ON (ms) |
|---|---|---|---|---|---|---|---|---|---|---|
| read_write | 32 | 360,764 | 158,865 | 260,171 | 18,038 | 7,943 | 13,009 | 1.77 | 4.03 | 2.46 |
| read_write | 64 | 484,988 | 190,654 | 307,735 | 24,249 | 9,533 | 15,387 | 2.64 | 6.71 | 4.16 |
| read_write | 128 | 556,021 | 194,301 | 301,791 | 27,801 | 9,715 | 15,387 | 4.60 | 13.17 | 9.81 |
| write_only | 32 | 318,879 | 108,696 | 188,760 | 53,146 | 18,116 | 31,460 | 0.60 | 1.77 | 1.02 |
| write_only | 64 | 345,766 | 117,533 | 197,251 | 57,628 | 19,589 | 32,875 | 1.11 | 3.27 | 1.95 |
| write_only | 128 | 356,725 | 89,144 | 202,884 | 59,454 | 14,857 | 33,814 | 2.15 | 8.61 | 3.78 |
结果很明显: 关掉 FPW 性能最好作为基线, 开了 FPW 之后性能掉到基线的 ~25%, 而 DWB 能保持 ~57%. write_only 128 并发的场景下 DWB 是 FPW 吞吐的 2.3 倍, 延迟也全面优于 FPW.
代码
改好的代码在这: https://github.com/baotiao/postgres
整个内核改动是用 Claude Code 完成的, 算是验证了一下 AI 改数据库内核代码这事确实能干.
BTW: 欢迎关注 AliSQL Alibaba’s Enterprise MySQL Branch with DuckDB OLAP & Native Vector Search