MongoDB 写操作详细流程分析【何时产生 WT 事务、何时写入 journal、何时生成/写入 oplog、以及最终提交/刷盘】
本文 从 WiredTigerRecoveryUnit 入手的逐层、深入解析:包含主要数据结构、关键方法(开始/提交/回滚)、与 WiredTiger API 的映射、与 oplog/OpObserver 的交互、以及完整的执行伪代码和典型调用链(方便你在源码中精确定位追踪)。我会把重点放在“在一次写操作中,何时产生 WT 事务、何时写入 journal、何时生成/写入 oplog、以及最终提交/刷盘”的各个细节上。
说明:下面的函数名与类名基于 MongoDB 源码常见命名(如
WiredTigerRecoveryUnit、WiredTigerKVEngine、WiredTigerRecordStore、OpObserverImpl等)。我会尽量把实现逻辑以伪代码 + 调用链的形式呈现,便于把说明与源码行数一一对应追踪
一、核心概念与主要对象(先梳理清楚)
-
RecoveryUnit(RU):MongoDB 内部用于管理单次操作/事务与存储引擎事务边界的抽象。
WiredTigerRecoveryUnit是其 WiredTiger 实现。每个OperationContext(一次 client 请求上下文)通常会持有一个 RU。- 负责:开始/提交/回滚 WT 事务、注册
Change回调(用于在 commit/abort 时触发回调)、管理 snapshot(读一致性)以及跟踪 in-memory 临时变更。
- 负责:开始/提交/回滚 WT 事务、注册
-
WT_SESSION(session):WiredTiger 的会话对象,承载
txn_begin/txn_commit/txn_rollback。WiredTigerRecoveryUnit内部持有或借用一个WT_SESSION *(或者 session 的包装)以执行底层 API。 -
WiredTigerKVEngine / WiredTigerRecordStore:KV 层负责把 MongoDB 操作映射到 WiredTiger 的表/索引 insert/update/delete 调用,实际向 WiredTiger 发起底层修改。
-
OpObserver / oplog:在执行写操作时,MongoDB 的业务层会通过 OpObserver 在合适时机向
local.oplog.rs插入一条 oplog entry。为保证原子性(数据变更与对应 oplog 要么同时生效,要么都不生效),MongoDB 把 oplog 插入与数据变更放入同一个 RU / WT 事务内提交。 -
WiredTiger WAL / journal:WT 维护自己的 log buffer 与 journal 文件(MongoDB 中
dbPath/journal/WiredTigerLog.*)。当WT_SESSION->txn_commit被调用时,WT 会把对应的 log record 写进其内存缓冲(并根据配置做 group-commit / flush)。j:true(客户端请求要求 journaling)会触发更强的持久化保证(触发 fsync/journal flush)。 -
Timestamp 机制(MongoDB 的时间戳支持):MongoDB 在支持多文档事务和快照读时,会在 RU/WT 中管理 commitTimestamp、stableTimestamp、oldestTimestamp 等(由
WiredTigerKVEngine与 replication 层协同维护),用于确定哪些数据对读者可见以及 checkpoint 时间点。这个机制影响 checkpoint/rollback 和可见性,但主要逻辑在WiredTigerKVEngine/TimestampMonitor中。
二、核心字段(WiredTigerRecoveryUnit 典型成员)
(便于你在源码中快速识别)
_session/_sessionCacheEntry:包装好的 WT_SESSION。_active/_inUnitOfWork:标记是否已开始一个 WT 事务 / 单元工作。_commitTimestamp/_prepareTimestamp:如果在事务或 prepare 情况下,会记录相关时间戳(仅在支持事务时)。_changes:在 RU 上注册的Change回调集合(commit/abort 时触发)。_roundUpToStableTimestamp/_durableTimestamp(与 timestamp 管理相关)。_prepared:标记事务是否处于 prepare 状态(multi-doc transactions)。_hasUncommittedOps:表示 RU 内是否有实际写操作(影响是否需要触发 WT 的提交逻辑)。
三、典型的写入执行流程(端到端,含关键函数与伪代码)
下面给出完整的高层流程(从接收到写请求到向客户端返回),随后展开 RU 层的详细伪代码。
- Client → mongod 接收写请求(例如 insert/update/delete)
- 在
OperationContext中创建/获取WiredTigerRecoveryUnit(RU) - 业务层(CRUD 操作)调用 record store 的 insert/update 等(这些调用使用当前 RU 的 WT_SESSION 执行 WT 操作)
- OpObserver(或 transaction commit 逻辑)构造并把 oplog 插入
local.oplog.rs(这一步也使用相同 RU) - RU 提交:
WiredTigerRecoveryUnit::commitUnitOfWork()调用WT_SESSION->txn_commit或等价包装 - WiredTiger 把事务的 log record 写入 log buffer;根据配置(group-commit / j:true)触发 sync 到 journal 或等待下一次 group commit
- Replication 层处理 writeConcern(例如等待 majority ack 或等待 journal flush),最终向 client 返回
- 后台线程会定期 checkpoint,把脏页写回数据文件;journal 中的记录帮助 crash-recovery 时回放(checkpoint + replay journal)
RU 层(WiredTigerRecoveryUnit)的伪代码(最关键路径)
class WiredTigerRecoveryUnit : public RecoveryUnit {
WT_SESSION* _session;
bool _inUnitOfWork = false;
vector<Change*> _changes;
Timestamp _commitTimestamp;
bool _hasWrites = false;
// Called when operation needs to start a storage transaction
void beginUnitOfWork() {
if (!_inUnitOfWork) {
_session = _sessionCache->getSession(); // get/checkout WT_SESSION
// begin a WT transaction with possible snapshot/timestamp settings
// e.g., WT_SESSION->txn_begin(_session, NULL);
wt_txn_begin(_session, _beginConfig);
_inUnitOfWork = true;
}
}
// Called by record-store writes to mark that writes happened
void markHasWritten() { _hasWrites = true; }
// registerChange: push Change objects that have commit/rollback callbacks
void registerChange(Change* change) {
_changes.push_back(change);
}
// Commit UI (called at end of OperationContext or UnitOfWork)
void commitUnitOfWork() {
if (!_inUnitOfWork) return;
// 1) Run pre-commit hooks? (some metadata ops)
for (Change* c : _changes) c->preCommit();
// 2) If using prepare / transaction timestamps set them (for multi-doc txns)
if (_commitTimestamp) {
// set WT transaction timestamp via WT extensions (if enabled)
wt_txn_set_commit_timestamp(_session, _commitTimestamp);
}
// 3) WT commit (this writes log record to WT log buffer)
int ret = WT_SESSION->txn_commit(_session, NULL);
if (ret != 0) { // handle error -> run abort callbacks below
for (Change* c : reverse(_changes)) c->rollback();
_sessionCache->releaseSession(_session);
_inUnitOfWork = false;
throw Exception;
}
// 4) Run commit callbacks
for (Change* c : _changes) c->commit();
// 5) release session back to pool
_sessionCache->releaseSession(_session);
_inUnitOfWork = false;
_changes.clear();
_hasWrites = false;
}
// Rollback
void abortUnitOfWork() {
if (!_inUnitOfWork) return;
WT_SESSION->txn_rollback(_session, NULL);
for (Change* c : reverse(_changes)) c->rollback();
_sessionCache->releaseSession(_session);
_inUnitOfWork = false;
_changes.clear();
_hasWrites = false;
}
}
说明与要点:
WT_SESSION->txn_commit是关键:它把该事务的修改写入 WT 的 log buffer,并将事务标记为已提交(对 WT/其他 sessions 可见)。但这不必然意味着数据已被 fsync 到磁盘(取决于 group-commit / j:true / commitIntervalMs 设置)。Change接口用于注册在 commit/rollback 时需要执行的额外逻辑(例如事务日志里生成后置操作、变更 Index 的元数据、发布 OpObserver 回调等)。OpObserver的一些工作会通过Change注册在 RU 上,以便在 commit 时执行真实的副作用(或回退时撤销)。- 如果在 commit 过程中发生错误,RU 必须回滚 WT 事务并触发已注册的
rollback()回调,以保持系统一致性。
四、oplog 的写入如何与 RU/WT 结合(保证原子性与可复制性)
关键原则:oplog 插入 与 数据修改 在同一 WT 事务中。实现上通常如下:
-
应用层开始 RU(
beginUnitOfWork())。 -
执行 collection 的数据操作(insert/update/delete),这些操作由
WiredTigerRecordStore使用 RU 的_session发起 WT 操作。 -
在写操作结束前,业务层生成一个 oplog document(包含 op type、ns、o、o2、ts、wall、txnNumber 等字段),并通过
OpObserver写入local.oplog.rs:OpObserverImpl::onInserts/onUpdate等会在合适时机调用repl::logOp或直接 insert 到 oplog collection。- 重要:oplog 插入也使用当前 RU 的 session 执行底层 insert(因此它会成为同一 WT 事务的一部分)。
-
最终调用
commitUnitOfWork(),触发 WT 的txn_commit,使得数据变更与 oplog 插入同时“提交”。
这样确保:要么 primary 的数据与对应的 oplog 同时可见(对 secondary 可复制),要么都不生效(例如在 crash 或 abort 的情况下)。
五、j:true(写入请求要求 journaling)如何影响路径
-
当客户端请求
j:true:- MongoDB 在确认写已被持久化前不会回应客户端。
- 实现上,
WiredTigerRecoveryUnit::commit在WT_SESSION->txn_commit返回后,需要确保 WT 日志已 flush 到磁盘(journal)。 - WiredTiger 自身支持 group-commit。
WT_SESSION->txn_commit会把 log record 写到内存,若请求了显式 sync,MongoDB 会调用 WT 的log->sync或依赖 WT 的log_write参数。MongoDB 会根据配置触发fsync或使用 WT 提供的日志 flush 接口,以满足j:true的要求。 - 这通常通过
WiredTigerKVEngine::logFlush()或类似封装函数来实现(在源码中搜索journal或waitForJournalFlush可找到具体实现)。
六、与 timestamp / snapshot 的关系(高级 — 事务与稳定性)
MongoDB 的时间戳机制(用于支持快照读和 multi-document transactions)会与 RU 紧密配合:
- 在 prepare/commit 情形下,RU 会在提交前设置 WT 事务的 commit timestamp(通过 WT 的扩展 API),使得该事务的 visibility 与磁盘上的 durable timestamp 协调。
WiredTigerKVEngine维护stable_timestamp与oldest_timestamp,并定期向 WT 通知这些时间点,以便 checkpoint 不会丢弃仍需用于读一致性的历史版本。- 这套机制确保:读者持有某个 snapshot 时间戳时,WT 中对应版本的数据仍可以被保留直到 checkpoint 之后(否则会影响 snapshot reads)。
(源码线索:WiredTigerKVEngine::setStableTimestamp、WiredTigerRecoveryUnit::setCommitTimestamp 等;在 src/mongo/db/storage/wiredtiger/ 下可找到。)
七、崩溃恢复流程(使用 journal + checkpoint)
-
MongoDB 正常运行时,WT 周期性 checkpoint(把数据页写入数据文件)并产生 checkpoint timestamp。
-
WT 的 journal 存放 checkpoint 之后到当前的 log changes(log records),这些 journal 文件位于
dbPath/journal下。 -
若 mongod 非正常崩溃启动恢复:
- WiredTiger 会先加载最近的 checkpoint(数据文件),然后 replay journal 中的 log records,把变更应用到数据文件,直到达到最后的已提交点(最后的 txn_commit)。
- replay 时也会校验 log record 的校验和并应用。
-
恢复完成后,mongod 进入正常服务流程。oplog 的条目(在 local.oplog.rs)会继续作为复制/恢复的参考(在 replica set 场景中,Secondary 可能会基于 oplog 进一步 catch-up)。
八、典型的调用链(写操作到 commit —— 以 insert 为例)


九、源码定位提示(马上就能打开的位置索引)
在 MongoDB 源码仓库中(src/mongo/db/storage/wiredtiger/)重点查看以下文件(文件名基于常见 repository 布局):
wiredtiger_recovery_unit.h/wiredtiger_recovery_unit.cpp:RU 的声明与实现(开始/提交/回滚、Change 管理、与 WT API 交互)。wiredtiger_kv_engine.cpp:初始化 WT 引擎时拼接wiredtiger_open的 config(其中包含log=(enabled=true,path=journal,compressor=snappy)等),以及 timestamp/flush/backup 相关接口。wiredtiger_record_store.cpp:collection-level insert/update/delete 的底层实现(如何调用 WT_CURSOR/WT_SESSION)。repl/oplog.cpp或repl/replication_coordinator/oplog.cpp:构建并插入 oplog 的逻辑(OpObserver 触发点)。storage/wiredtiger/wt_session_cache.*:session 池的实现(如何复用 WT_SESSION)。wt工具与 WiredTiger 源码(如果要看 WT 的日志格式/record header/txn_commit 实际效果)在 WiredTiger 项目中(例如log目录下的实现)。
本文来自博客园,作者:NeoLshu,转载请注明原文链接:https://www.cnblogs.com/neolshu/p/19120600

浙公网安备 33010602011771号