WAL (Write-Ahead Logging) 架构指南

张开发
2026/4/19 2:18:39 15 分钟阅读

分享文章

WAL (Write-Ahead Logging) 架构指南
WAL (Write-Ahead Logging) 架构指南目录概述核心原理WAL 工作机制实现细节优势与权衡实际应用与其他技术对比最佳实践概述Write-Ahead Logging (WAL) 是一种用于保证数据完整性和持久性的技术广泛应用于数据库系统、文件系统和分布式存储中。其核心思想是在将数据修改应用到实际数据存储之前先记录所有修改操作的日志。历史背景WAL 概念最早在 1976 年由 System R 项目提出随后被广泛应用于各种数据库系统中。PostgreSQL、MySQL (InnoDB)、SQLite、MongoDB 等主流数据库都采用 WAL 机制来保证 ACID 特性。为什么需要 WAL在传统的数据存储系统中直接修改数据文件存在以下问题原子性问题修改多个数据块时如果系统崩溃可能导致部分修改生效、部分未生效性能问题每次修改都需要同步写入磁盘随机 I/O 开销大恢复困难崩溃后难以确定哪些数据是一致的WAL 通过将所有修改先写入顺序日志文件解决了这些问题。核心原理WAL 的两条黄金规则WAL 的正确性依赖于以下两条核心规则规则 1日志优先在数据页被写入非易失性存储之前包含该数据页修改的所有日志记录必须先写入非易失性存储。这意味着任何数据修改必须先记录在日志中日志写入必须是持久化的通过 fsync 确保落盘只有在日志持久化后才能修改实际数据规则 2日志顺序日志记录必须按事务提交的顺序写入。这确保了崩溃恢复时可以按正确顺序重放日志保证事务的原子性和一致性WAL 的关键概念LSN (Log Sequence Number)LSN 是日志序列号单调递增用于标识日志记录的位置和顺序。每个日志记录都有一个唯一的 LSN。LSN 1: BEGIN T1 LSN 2: UPDATE page 100, row 5 LSN 3: COMMIT T1CheckpointCheckpoint 是检查点标记日志中某个位置之前的所有修改都已应用到数据文件中。Checkpoint 之后的日志记录在恢复时需要重放。|----- 已应用 -----|-------- 待恢复 --------| ^ Checkpoint LSNLog Buffer日志缓冲区是内存中用于缓存日志记录的区域通过批量写入减少磁盘 I/O。WAL 工作机制正常操作流程┌─────────────────────────────────────────────────────────────┐ │ WAL 正常写入流程 │ └─────────────────────────────────────────────────────────────┘ 1. 事务开始 │ ▼ 2. 写入 BEGIN 日志到 Log Buffer │ ▼ 3. 执行数据修改在内存缓冲区 │ ▼ 4. 为每个修改操作生成日志记录 │ ▼ 5. 写入日志记录到 Log Buffer │ ▼ 6. 事务提交 │ ▼ 7. 写入 COMMIT 日志到 Log Buffer │ ▼ 8. fsync() - 将 Log Buffer 刷入磁盘 │ ▼ 9. 返回成功给客户端 │ ▼ 10. 异步将脏页刷入数据文件崩溃恢复流程┌─────────────────────────────────────────────────────────────┐ │ WAL 崩溃恢复流程 │ └─────────────────────────────────────────────────────────────┘ 1. 系统重启 │ ▼ 2. 读取最后一个 Checkpoint 记录 │ ▼ 3. 从 Checkpoint LSN 开始扫描日志 │ ▼ 4. 对于每个日志记录 ├─ 如果是已提交事务重做Redo └─ 如果是未提交事务撤销Undo │ ▼ 5. 恢复完成系统可用Redo 和 UndoRedo (重做)Redo 用于重放已提交事务的修改确保所有已提交的修改都真正生效。日志记录示例 { LSN: 100, type: UPDATE, page: 42, offset: 128, before: old_value, after: new_value, status: COMMITTED } Redo 操作 将 page 42 的 offset 128 处的值设置为 new_valueUndo (撤销)Undo 用于回滚未提交事务的修改确保事务的原子性。日志记录示例 { LSN: 100, type: UPDATE, page: 42, offset: 128, before: old_value, after: new_value, status: UNCOMMITTED } Undo 操作 将 page 42 的 offset 128 处的值恢复为 old_value实现细节日志记录格式典型的 WAL 日志记录包含以下字段structWALRecord{uint64_tlsn;// 日志序列号uint32_trecord_type;// 记录类型BEGIN/COMMIT/UPDATE/DELETE等uint64_ttransaction_id;// 事务IDuint32_tpage_id;// 修改的数据页IDuint32_toffset;// 页内偏移量uint32_tdata_length;// 修改数据长度uint8_tbefore_image[];// 修改前的数据用于Undouint8_tafter_image[];// 修改后的数据用于Redouint32_tchecksum;// 校验和};日志文件管理日志文件切换当日志文件达到一定大小时需要切换到新的日志文件wal_000000010000000000000001 // 当前活跃日志 wal_000000010000000000000002 // 下一个日志文件日志清理策略基于 Checkpoint 清理Checkpoint 之前的日志可以删除基于时间清理保留最近 N 天的日志基于大小清理保留总大小不超过 N 的日志性能优化技术组提交 (Group Commit)将多个事务的日志记录合并后一次性 fsync减少磁盘 I/O 次数。传统方式 事务1 - fsync - 事务2 - fsync - 事务3 - fsync 3次磁盘同步 组提交 事务1 事务2 事务3 - fsync 1次磁盘同步日志缓冲区使用内存缓冲区缓存日志记录批量写入Log Buffer (内存) ┌─────────────────────────────┐ │ Record 1 │ │ Record 2 │ │ Record 3 │ │ ... │ └─────────────────────────────┘ ↓ 满时刷盘 WAL File (磁盘)零拷贝技术使用mmap或sendfile减少数据拷贝开销。优势与权衡优势优势说明原子性保证通过日志重放/撤销保证事务的原子性持久性保证日志先落盘确保已提交事务不丢失性能提升顺序写日志比随机写数据页快得多快速恢复只需重放日志无需扫描整个数据文件支持时间点恢复可以恢复到任意时间点便于复制日志可用于主从复制权衡与挑战挑战说明写放大数据需要写两次日志 数据文件日志空间需要额外的存储空间存储日志fsync 开销频繁的 fsync 影响性能实现复杂度正确实现 WAL 需要处理各种边界情况日志清理需要合理的管理策略避免日志无限增长性能对比操作无 WAL有 WAL单次写入随机 I/O慢顺序 I/O快崩溃恢复需要全量扫描只需重放日志事务提交需要同步数据页只需同步日志并发写入锁竞争严重写入日志锁竞争小实际应用PostgreSQLPostgreSQL 使用 WAL 实现了完整的 ACID 特性-- 查看 WAL 配置SHOWwal_level;-- minimal, replica, logicalSHOWwal_buffers;-- 日志缓冲区大小SHOWcheckpoint_timeout;-- Checkpoint 间隔-- 手动切换 WAL 文件SELECTpg_switch_wal();PostgreSQL 的 WAL 特点支持 WAL 归档用于时间点恢复支持逻辑复制支持物理流复制MySQL InnoDBInnoDB 的 redo log 是 WAL 的典型实现innodb_log_file_size 512M # 每个 redo log 文件大小 innodb_log_files_in_group 2 # redo log 文件数量 innodb_flush_log_at_trx_commit 1 # 事务提交时的刷盘策略innodb_flush_log_at_trx_commit参数0每秒写入一次不 fsync1每次提交都 fsync最安全最慢2每次提交写入每秒 fsyncSQLiteSQLite 使用 WAL 模式提供更好的并发性能-- 启用 WAL 模式PRAGMA journal_modeWAL;-- 查看 WAL 状态PRAGMA journal_mode;SQLite WAL 特点读写可以并发写写仍然串行-wal 和 -shm 文件MongoDBMongoDB 使用 oplog (operations log) 实现 WAL// 查看 oplog 状态db.getReplicationInfo()// 查看 oplog 条目db.oplog.rs.find().sort({$natural:-1}).limit(5)与其他技术对比WAL vs Shadow Paging特性WALShadow Paging写入方式追加写入写时复制空间开销日志文件原始数据副本写性能顺序写快随机写慢实现复杂度中等简单长事务支持好差空间膨胀WAL vs ARIESARIES (Algorithms for Recovery and Isolation Exploiting Semantics) 是基于 WAL 的恢复算法增加了事务嵌套支持保存点Savepoint更精细的锁机制WAL vs LSM Tree特性WALLSM Tree主要用途事务恢复写优化存储数据组织原地更新追加更新读性能好需要合并写性能好极好压缩无需要最佳实践配置建议1. 日志缓冲区大小# PostgreSQL wal_buffers 16MB # 通常设置为 shared_buffers 的 3% # MySQL InnoDB innodb_log_buffer_size 16MB2. Checkpoint 策略# PostgreSQL checkpoint_timeout 5min checkpoint_completion_target 0.9 # MySQL InnoDB innodb_max_dirty_pages_pct 753. 日志文件大小# PostgreSQL min_wal_size 1GB max_wal_size 4GB # MySQL InnoDB innodb_log_file_size 512M监控指标指标说明告警阈值WAL 写入速率每秒写入的字节数持续高于磁盘容量WAL 同步延迟fsync 耗时 10msWAL 文件数量当前 WAL 文件数 预期值Checkpoint 频率Checkpoint 触发频率过于频繁恢复时间预估预计崩溃恢复时间 5分钟常见问题问题 1WAL 增长过快原因Checkpoint 间隔过长大事务过多归档未配置解决方案-- PostgreSQLALTERSYSTEMSETcheckpoint_timeout5min;ALTERSYSTEMSETarchive_modeon;问题 2fsync 延迟高原因磁盘性能差fsync 频率过高解决方案使用 SSD调整innodb_flush_log_at_trx_commit使用电池备份缓存 (BBWC)问题 3恢复时间过长原因Checkpoint 间隔过长WAL 文件过大解决方案-- 增加 Checkpoint 频率ALTERSYSTEMSETcheckpoint_timeout1min;-- 使用增量备份总结WAL 是现代数据库和存储系统的核心技术通过将修改先写入日志再应用到数据实现了数据完整性保证 ACID 特性高性能顺序写代替随机写快速恢复基于日志的恢复机制可扩展性支持复制、备份等功能理解 WAL 的原理和实现对于设计和使用高可靠、高性能的存储系统至关重要。

更多文章