上一篇讲 Cache 淘汰,回答了「查询为什么会抖」。这一篇讲 journal 和持久化,回答另一个根本问题:写入怎么保证不丢,崩溃后怎么恢复。这是所有数据库都必须回答的问题,MongoDB 的答案落在 journal(预写日志)和 checkpoint 这套机制上。
理解这套机制,能解释几个关键决策:为什么 MongoDB 的写入不是「立即落盘」而是「最多延迟 100ms」?为什么 w: 1 和 w: "majority" 的延迟差这么多?为什么 checkpoint 间隔影响崩溃恢复速度?这些都和 journal、checkpoint 的工作方式直接相关。
先把机制边界说清楚
WiredTiger 保证持久性的核心是 WAL(Write-Ahead Logging,预写日志),在 MongoDB 里叫 journal。它的规则很简单:任何修改内存页(Cache 脏页)之前,先把这次修改记到 journal。这样即使崩溃,journal 里有完整的修改记录,恢复时重放即可。
写入的完整链路是:
- 应用发起写入(insert/update/delete),复制集场景还要追加 oplog。
- 把修改记到 journal(WAL),最多延迟
commitIntervalMs(默认 100ms)刷盘。 - 修改 Cache 里的页(变成脏页),返回成功。此时数据在内存,还没进数据文件。
- checkpoint(默认 60s)把脏页刷成数据文件里的一致快照。
关键认知:journal 记录成功 = 数据不会丢(即使还没 checkpoint,崩溃也能从 journal 恢复)。而「写入返回成功」的那一刻,数据可能还在 Cache 里、没进数据文件——这就是为什么 MongoDB 的写入是「最多 100ms 后持久」,不是「立即持久」。
一次写入的不丢之路
把写入链路画出来,journal 的角色立刻清晰:它是介于「写入返回成功」和「数据真正进数据文件」之间的那道保险。没有 journal,Cache 里的脏页在崩溃时会全部丢失;有了 journal,脏页丢了也能从日志重放。
checkpoint:恢复快的保证
Checkpoint 是 WiredTiger 把 Cache 脏页刷成数据文件一致快照的机制,默认每 60 秒一次。它的价值不只是「让数据落盘」,更是缩短崩溃恢复时间。
如果没有 checkpoint,崩溃恢复要回放从头到尾的全部 journal,journal 越长恢复越慢。有了 checkpoint,恢复时只要:
- 加载最近一次 checkpoint 的数据文件(一个一致的磁盘快照)。
- 回放 checkpoint 之后到崩溃点的 journal(这一段通常很短,最多 60 秒的写入)。
- 丢弃未提交的部分(journal 没记的 = 没成功的)。
所以 checkpoint 间隔短 = 恢复快(要回放的 journal 少);checkpoint 间隔长 = 恢复慢但刷盘频率低。这是个性能和恢复速度的权衡,默认 60 秒是个平衡点。
journal 的刷盘策略
journal 什么时候真正写到磁盘,由 storage.journal.commitIntervalMs(默认 100ms)控制。写入记到 journal 后,最多等 100ms 才批量刷盘。这种「攒一批再刷」的方式,把大量小写入合并成少量大写入,提升吞吐,代价是「最多 100ms 的持久延迟」。
有两个例外会让 journal 立即刷盘:
- 写关注带
j: true:强制这次写入等 journal 刷盘成功才返回。这是「单机不丢」的强保证,但要承受刷盘延迟。 - 写入量触发提前刷盘:journal 缓冲区攒到一定大小,会提前刷盘,不等 100ms。
理解了刷盘策略,就理解了 w: 1 和 j: true 的差别:w: 1 默认不等 journal 刷盘(只是记到 journal 缓冲),j: true 才等真正刷盘。所以严格的单机持久性要 w: 1, j: true,不是单纯的 w: 1。
writeConcern:持久性的档位选择
writeConcern 是 MongoDB 控制写入持久性和一致性的参数,它决定了「写入返回成功前要等什么」。三个核心档位:
w: 0:不等任何确认,发了就算成功。最快,但可能丢。只适合可丢失的数据(日志、埋点、缓存预热)。
w: 1(默认):等主节点确认(记到 journal 缓冲或内存)。单机不丢(配合 j: true),但在复制集下,如果主节点记完就返回、还没来得及同步到从节点,此时主节点宕机,这条写入可能在 failover 后回滚丢失。所以 w: 1 是「单机持久,复制集可能回滚」。
w: "majority":等多数节点确认写入。这是复制集下最强的不丢保证——多数节点都有这条数据,任何少数派宕机都不会丢。代价是延迟:要等多数节点同步确认,写入延迟通常是 w: 1 的两倍以上。
档位选择的核心判断是「这条写入能不能容忍丢失」:
- 可丢(日志、埋点、计数):
w: 0或w: 1,换吞吐。 - 单机不丢即可(大多数业务):
w: 1,必要时j: true。 - 绝对不能丢(订单、支付、账户):
w: "majority",接受延迟代价。
崩溃恢复的完整流程
把前面几点串起来,崩溃恢复的完整流程是:
- mongod 重启,WiredTiger 打开数据文件,加载最近 checkpoint 的快照。
- 检查 journal,找到 checkpoint 之后的记录。
- 逐条重放这些 journal 记录,把已提交的修改重新应用到数据页。
- 丢弃 journal 里没有的(没记 = 没提交 = 丢弃)。
- 恢复完成,开始正常服务。
这个流程的关键是「journal 完整性」——只要 journal 没坏,已提交的写入就不会丢。journal 文件本身也是循环的(旧的 checkpoint 后会被删除),不会无限增长。
取舍与边界
这套 WAL + checkpoint 机制的优势是「写入快(journal 追加 + 内存改)+ 恢复可靠(journal 回放)」。代价和边界:
- 写入不是立即持久。默认最多 100ms 延迟才真正落 journal。绝对不能丢的写入要
j: true或w: "majority"。 - checkpoint 期间 IO 上升。刷脏页带来 IO 尖峰,可能影响同时刻的其他操作。
- 复制集下
w: 1仍可能回滚。主切换时,没同步到多数派的写入会被回滚。核心数据要w: "majority"。 - journal 占用磁盘。journal 文件要留够空间,磁盘满了 journal 写不进去,写入会失败。
判断框架
- 写入不丢靠 journal,不是靠「写入返回成功」。成功返回 ≠ 已落盘。
j: true是单机持久性强保证,w: "majority"是复制集不丢保证。- checkpoint 间隔短 = 恢复快 + 刷盘频繁;间隔长 = 恢复慢 + 刷盘少。
- writeConcern 按数据价值选档:可丢
w:0/1,单机不丢w:1+j:true,绝对不丢w:majority。 - 复制集下
w:1在主切换时可能回滚,核心交易必须w:majority。 - 崩溃恢复时间 ≈ 重放 checkpoint 之后的 journal,保持 checkpoint 间隔合理。
下一篇讲压缩,把「存储怎么省」这条线补上。
关于十三Tech
我是十三,All in AI Agent 方向的架构师,专注 AI 工程实践。
我相信 AI 是程序员的最佳搭档,也希望帮助每一位开发者更好地驾驭 AI。
如果你想继续跟完这套「图解 MongoDB」,欢迎关注公众号 「十三Tech」。后续会按存储引擎、高可用和分片集群这条线更新。

