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

结果如下:

image

场景 并发 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


<
Previous Post
为什么我认为 MySQL 比 PostgreSQL 集成 DuckDB 更加的优雅?
>
Blog Archive
Archive of all previous blog posts