闭嘴吧,双写神棍!PostgreSQL不需要你那过时的“补丁”
本期播客
闭嘴吧,双写神棍!PostgreSQL不需要你那过时的“补丁”
各位 PostgreSQL 数据库圈的同仁,最近社区又冒出一个“神奇”的提议:把MySQL的Double Write Buffer移植给PostgreSQL,说是为了解决大缓存实例重启后的恢复速度问题。
作为一个PG老兵,看到这种提议,我第一反应是: 这是用治疗脚气的药膏去敷秃顶的脑袋 —— 完全搞错了病灶。
今天,我就从全栈维度,用第一性原理撕开这个伪命题的画皮。我们要怼的不是这位开发者的热情,而是那种 “把别人家的祖传秘方当万灵丹”的偷懒思维。
一、Double Write的本质:MySQL的“体外碎石机”,自带内伤
首先,我们必须清醒地认识到Double Write在MySQL/InnoDB中存在的根本原因。它本质上是一个 “补丁” ,是为了修复MySQL在数据页写入方面的一个“先天性缺陷”。
1. 物理层的内伤:写放大与锁冲突
InnoDB的Double Write Buffer,说白了就是“写两次”。数据页先写到共享表空间的双写缓冲区,再写到真正的数据文件。这直接带来了两个物理层面的灾难:
写放大:每一次页面写入,磁盘I/O直接翻倍。在SSD寿命宝贵、IOPS紧张的今天,这种操作堪称奢侈。 锁冲突的噩梦:有开发者曾给MySQL提交Bug报告指出,当Doublewrite Buffer的slot被占满时, pthread_cond_wait会直接让整个系统陷入阻塞,因为单页刷新的设计在重负载下会成为绝对的瓶颈。这不是我臆想的,这是MySQL Bug系统里血淋淋的记录。
2. 性能的滑铁卢:3倍惩罚
数据不会说谎。MySQL 8.0.20版本曾因为调整了 innodb_doublewrite_pages 的默认值(从128降为4),导致了3-5倍的表重建性能暴跌。
这是什么概念? 原本10分钟做完的 ALTER TABLE,现在要等半小时以上。根本原因? 就是因为Double Write的批处理能力被削弱,频繁的、小批量的I/O操作让磁盘同步开销直接爆表。
结论:Double Write是一个为了修复“页断裂”而不得不吞下的毒药。它解决了原子性问题,代价是牺牲了性能和寿命。
二、PG的基因:从第一性原理出发,我们不需要“双写”
那么,PostgreSQL为什么不需要这个“补丁”?因为PostgreSQL的基因里,就没有得过MySQL那种病。
1. Full Page Writes:PG的“体内免疫系统”
MySQL的“页断裂”问题在于,如果一个16KB的页在写入过程中只写了8KB就断电了,这个页就烂了。InnoDB需要用Double Write来保证这个页是完整的。
但PostgreSQL的WAL机制在设计上就更底层、更聪明。当开启 full_page_writes 时,PostgreSQL在每次Checkpoint后首次修改页面时,会把整个页面的内容写入WAL 。
这意味着什么? 即使发生断裂,重启恢复时,PG可以从WAL中捞出那个完整的页面镜像,直接覆盖损坏的页,然后再应用后续的修改日志。 本质区别:MySQL选择在“存储引擎层”用一个备份区域(Double Write)来保底;PG选择在“日志层”用更精细的页面镜像来重建。 这是逻辑复制与物理备份的思维差异。
2. 没有“断裂”的恐惧,只有“恢复”的效率
让我们回到问题的起点: 为什么开发者想引入Double Write?是为了解决大缓存实例重启后的恢复速度。
仔细想想,PG恢复慢,是因为怕“页断裂”吗?不是!PG的恢复速度瓶颈在于WAL日志的回放过程。PG的恢复是“串行”的,它必须一个一个地读取WAL记录,一个一个地修改数据页。
如果PG引入了Double Write,会发生什么?
写路径多了一道“双写”的闸门, 正常运行时性能先掉10% 。 重启恢复时,它不仅要读WAL,还得先去Double Write区域捞数据, 恢复路径变得更长、更复杂。
三、真正的屠龙术:Pipelined Recovery和并行预加载
我们要解决“大缓存实例重启恢复慢”,不是去抄别人的过时作业,而是要优化PG自己的核心流程。事实上,真正的PG内核专家已经在路上了。
1. Pipelined Recovery:让恢复像流水线一样飞
就在2026年1月,PostgreSQL黑客邮件列表中出现了一个极具价值的Patch: Pipelined Recovery 。
这个方案的核心思想是什么?并行化。
以前的恢复:启动进程单线程解码WAL -> 单线程应用WAL -> 单线程等待I/O。 Pipelined Recovery: 创建一个流水线,用一个后台工作进程(bgw)负责解码WAL并填充共享队列,而启动进程(redo apply loop)直接从队列里取已经解码好的记录进行应用。
数据支撑:
在 simple-update测试中,恢复时间从20.16秒缩短到 11.58秒,性能提升42.56% 。在 tpcb-like测试中,恢复时间从20.59秒缩短到 13.64秒,性能提升33.75% 。
这才是解决恢复速度的正确姿势: 让CPU的解码工作与I/O的应用工作重叠,而不是让CPU在那傻等I/O。
2. Recovery Prefetch:让数据“预热”不再是玄学
另一个正在演进的方向是 recovery_prefetch。PG正在尝试在恢复过程中,提前预取那些即将被修改的缓冲区。结合pg_prewarm等预热技术,我们可以让重启后的缓存快速升温,而不是等到用户查询来了才去磁盘拉数据。
四、前提条件的崩塌:你拿来对比的“大缓存”,根本不在一个维度
那些想引入Double Write的开发者,往往假设“大缓存”是所有数据库的通用痛点。
但根据第一性原理分析,这个前提本身就是错的:
MySQL的“大缓存”痛点:MySQL重启后,Buffer Pool是空的,Double Write的存在是为了防止它从磁盘读数据时读到“半拉子”页。它的恢复慢,一部分原因是它不得不处理那些因为自己写漏洞导致的烂摊子。 PG的“大缓存”痛点:PG重启后,Shared Buffer也是空的。但PG从不担心从磁盘读到烂页,因为有WAL日志和Full Page Writes兜底。PG慢,是因为它要应用大量的WAL日志来把数据库推到一致状态。 一个是修补物理破损,一个是推进逻辑状态,这是完全不同的计算模型。
数据维度:某MySQL团队优化为了优化RTO,不得不对Buffer Pool初始化、Redo Log扫描、Undo Log结构等七八个地方同时动刀。而PG的Pipelined Recovery,仅仅改动了一个“解码-应用”的流程,就拿到了 30%-40% 的提升。
这说明什么?说明PG的基线本身就比MySQL干净。 在一个干净的架构上做优化,事半功倍;在一个堆满补丁的架构上修修补补,事倍功半。
五、结语:尊重架构,拒绝“偷懒式”移植
数据库内核开发,最忌讳的就是 “经验主义” 和 “功能移植癖” 。
看到MySQL有Double Write,就想给PG也来一份,这是典型的 “拿着锤子看什么都像钉子” 。如果你真的想解决PG重启恢复慢的问题,请你去研究Pipelined Recovery,去研究recovery_prefetch,去优化Checkpoint的频率,去调教操作系统的预读策略。
别再抱着Double Write这个过时的“拐杖”来敲打PostgreSQL这架精密的“飞机引擎”了。 PG需要的是并行流水线,而不是双写拐杖。
PS: 那位提出Patch的开发者Imran Zaheer,他基于同事Ants Aasma的建议所做的Pipelined Recovery工作,才是真正值得全社区关注的瑰宝。请把聚光灯打在正确的方向上。