bigywater.tech
MongoDB · 57

事务在 oplog 里长什么样

事务的修改是怎么写进 oplog、再复制到 secondary 的?这篇提交一个事务,直接读 local.oplog.rs,看 applyOps 条目的结构、config.transactions 的记录,再对比中止和大事务的情形。

1 条对 3 条
事务里 3 个操作写进 oplog 的条数,对不放进事务时的 3 条
applyOps
事务提交时 oplog 里的命令:op=c、ns=admin.$cmd
2 条
20 个 1 MiB 的插入事务拆成的 applyOps 条数(第一条 partialTxn=True)

一条 applyOps

实测 · 本机执行输出
  新增了 1 条 oplog 条目
  条目的顶层字段: ['lsid', 'ns', 'o', 'op', 'prevOpTime', 't', 'ts', 'txnNumber', 'v', 'wall']
  op='c' ns='admin.$cmd';o 里的键 ['applyOps'];applyOps 里有 3 个操作:
    op=i ns=test.a  o={'_id': 10, 'v': 10}  o2={'_id': 10}
    op=u ns=test.a  o={'$v': 2, 'diff': {'u': {'v': 100}}}  o2={'_id': 1}
    op=d ns=test.a  o={'_id': 2}  o2=None
  lsid 的键 ['id', 'uid'];txnNumber = 2;prevOpTime.ts = Timestamp(0, 0)(事务的第一条,没有前驱)

事务提交时,所有操作被打包成一条 applyOps(op: "c"、ns: "admin.$cmd")写进 oplog,secondary 按这条原子地应用。

对比不放进事务

实测 · 本机执行输出
  新增了 3 条:
    op=i ns=test.a  o={'_id': 20, 'v': 20}  o2={'_id': 20}  lsid=无
    op=u ns=test.a  o={'$v': 2, 'diff': {'u': {'v': 300}}}  o2={'_id': 3}  lsid=无
    op=d ns=test.a  o={'_id': 10}  o2=None  lsid=无

同样三个操作不放进事务,各写一条 oplog;事务把它们合成了一条。

update 存 diff

实测 · 本机执行输出
    update 的 o = {'$v': 2, 'diff': {'u': {'v': 100}}}

applyOps 里的 update 条目是 diff({$v: 2, diff: {u: {v: 100}}}),不是整个新文档。

提交标记

实测 · 本机执行输出
  config.transactions 记录: {'txnNum': 2, 'lastWriteOpTime': {'ts': Timestamp(1791774542, 3), 't': 1}, 'lastWriteDate': datetime.datetime(2026, 10, 12, 3, 9, 2, 42000), 'state': 'committed'}
  与 applyOps 条目的 ts 相同: True

config.transactions 里该会话的记录 state: "committed",lastWriteOpTime 指向 applyOps 条目。

中止

实测 · 本机执行输出
  insert + update 之后 abortTransaction:oplog 里新增了 0 条;文档 _id 30 存在: False

中止的事务在 oplog 里不留任何条目。

大事务

实测 · 本机执行输出
  oplog 里新增 2 条 applyOps 条目:
    条目 1: applyOps 里 16 个操作;partialTxn=True;count=None;prevOpTime.ts=(0,0)
    条目 2: applyOps 里 5 个操作;partialTxn=None;count=21;prevOpTime.ts=Timestamp(1791774542, 7)

事务的修改太大时拆成多条 applyOps:前面的带 partialTxn: true,最后一条带总操作数 count,用 prevOpTime 链接。

分步演示

事务的 oplog
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / MongoDB 9.0.2(Homebrew 的 mongod、mongos)/ mongosh 2.12.0 / pymongo 4.18.3;每个实验在临时目录里起全新的 mongod(独立 dbpath,端口 9900 以上,只监听 127.0.0.1,WiredTiger 缓存 0.25 GB),需要副本集或分片集群时在本机起多个进程;命令输出来自真实的 mongosh,批量写入、计时与多线程用 pymongo;计时用 time.perf_counter,数字每次运行会有小幅抖动,ObjectId、时间戳等每次运行都不同。单节点副本集;直接用 pymongo 读 local.oplog.rs;事务外的对照用 retryWrites=False 的连接(默认的 retryable writes 会给事务外的写也加 lsid 与 txnNumber)
    • 没有验证:分片事务的 prepare / commit 条目;大事务的 applyOps 拆分阈值的精确值;change stream 里如何呈现事务内的多个操作(下面 change stream 一篇);事务在 secondary 上的应用方式与延迟;oplog 条目 v、t、wall 字段的含义
    京ICP备2026067502号-1京公网安备11010502063487号