必须日志先行,即先将Redo日志持久化到磁盘,再修改内存数据页,以确保事务提交后的持久性和崩溃一致性;否则断电时可能丢失修改且无法回滚或重做。

SQL数据库写入数据时,并不是先存数据再记日志,而是严格遵循“日志先行”(Write-Ahead Logging, WAL)原则:任何数据页的修改,必须先将对应的日志记录持久化到磁盘上的事务日志文件中,之后才能更新内存中的数据页,最终才可能刷盘到数据文件。
为什么必须日志先行?
核心目标是保证事务的持久性(Durability)和崩溃一致性(Crash Consistency)。如果先改数据页再写日志,一旦在写日志前发生断电或崩溃,内存中已修改但未落盘的数据页丢失,而日志里又没有对应记录,数据库重启后既无法回滚该事务,也无法重做——状态彻底不可恢复。
日志先行确保:只要事务被标记为“已提交”,其修改一定有可重放的日志记录;即使数据页尚未写入磁盘,重启后也能通过重做日志(Redo Log)准确还原。
一次INSERT操作的实际执行顺序
以一条简单 INSERT INTO users (name) VALUES ('Alice'); 为例,在支持WAL的数据库(如PostgreSQL、SQL Server、MySQL InnoDB)中,典型流程如下:
- 事务开始,分配唯一XID(事务ID)
- 在内存中生成一条red">Redo日志记录:描述“在users表第X页第Y槽插入一条包含'Alice'的记录”
- 强制将该Redo日志写入并同步(fsync)到磁盘日志文件
- 在Buffer Pool中定位或加载目标数据页,执行实际插入(仅内存操作)
- 记录Undo日志(用于回滚),也受WAL保护(即Undo日志本身也要先写日志)
- 事务提交时,再写一条“事务已提交”的日志并同步
- 数据页后续由后台线程异步刷盘(Checkpoint机制控制),不阻塞主流程
日志先行对性能与可靠性的平衡
WAL看似增加I/O开销,实则大幅提升整体效率与可靠性:
- 减少随机写:日志是追加写(Append-only),远比随机更新数据页快;数据页刷盘可批量、延迟、按需进行
- 支持组提交(Group Commit):多个事务的日志可合并一次fsync,显著降低磁盘同步次数
- 故障恢复确定性强:崩溃后只需扫描日志文件,从最后一个Checkpoint点开始重做(Redo),跳过已刷盘页,过程可预测、速度快
- 不牺牲ACID:即使启用缓存或延迟刷盘,只要日志落盘,提交即持久
常见误区提醒
注意区分几个易混淆概念:
- “日志写入” ≠ “日志刷盘”:写入缓冲区(如pg_wal buffer)不算数,必须调用fsync/FlushFileBuffers等系统调用真正落盘才算满足WAL
- Redo日志和Binlog不是一回事:MySQL中InnoDB的redo log是物理逻辑日志、循环复用、保障崩溃恢复;binlog是Server层的逻辑日志、追加写、用于主从复制和归档,二者协同但职责分离
- WAL不是所有引擎都默认启用:例如MySQL MyISAM不支持事务,无WAL;而InnoDB强制启用,不可关闭










