我最近在帮一个团队做数据库选型和架构评审,业务系统既要保留 InnoDB 的生态,又想把 PostgreSQL 的一些能力用起来,底座还是标准的存算分离。讨论到一半,团队负责存储的同事问了一句:“刷脏保序这个事情,两个引擎在存储分离以后到底谁更省心?”这个问题看起来不大,但再往下聊,几乎把两个引擎的底层哲学都翻了出来。这篇文章就是我那一次的整理:从崩溃恢复最基本的规则讲起,把 InnoDB 和 PostgreSQL 各自的选择拆开,再落到存算分离的工程实现。
先说结论:在传统本地磁盘上,两个引擎都能把刷脏安排得明明白白,谈不上谁比谁强多少;但是架构一旦变成“计算节点和存储节点分开”,InnoDB 那种把脏页按 LSN 严格排序的思路,和 PostgreSQL 那种几乎不看页间顺序、全靠 WAL 兜底的思路,会走向两条完全不同的工程路线。理解这两条路线,比单纯记住几个参数有用得多。
1. 刷脏为什么必须先讲“保序”:崩溃恢复的最底层规则
1.1 没有日志,数据库就不敢把脏页写盘
先回到最基础的问题:数据库为什么要刷脏?因为内存有限,数据页总得写回磁盘;因为崩溃恢复,数据页又不能随便写回磁盘。
假设一个数据页在内存里被修改了两次,第一次修改写进了一条日志,第二次修改又写进了一条日志。如果这个数据页在两条日志都还没落盘的情况下就被写回了磁盘,然后机器断电,磁盘上就有了“包含两次修改内容的数据页”,而日志文件里什么都没有。恢复时数据库不知道该页发生过什么,更不知道两次修改分别是什么,只能当作一个未提交的中间状态处理,这就会产生不可恢复的损坏。
所以数据库有一条铁律:数据页落盘之前,修改它的日志必须先落盘。这就是 WAL(Write-Ahead Logging)的本质,InnoDB 里的 redo log、PostgreSQL 里的 WAL,都是为了这条铁律服务的。
光有铁律还不够,数据库还得知道“这条日志对应哪个页、页上已经有哪些修改”。于是每个数据页的页头里都存了一个日志位置,InnoDB 是 LSN,PostgreSQL 是 pd_lsn。恢复时扫描日志,看到某条日志对应的 LSN 比页面上记录的 LSN 旧,说明这个页已经包含这次修改,跳过;反过来,如果页面上记录的 LSN 比日志新,说明这个页缺了这次修改,必须重放。
这个机
