bigywater.tech
MongoDB · 65

$set 一个字段,oplog 里存的是什么(diff 格式)

$set 一个字段,oplog 里存的是什么?$inc 存增量还是结果?改数组呢?这篇逐个操作符读 local.oplog.rs,看 diff 的记法,再比较 100 KB 文档改一个字段与整个替换的 oplog 大小。

165 对 100,157 字节
100 KB 的文档上 $inc 一个字段与 replace_one 的 oplog 条目大小
u: {a: 13}
$inc a 加 10 在 oplog 里记的是结果值 13,不是增量
0 条
没有实际变化的 update 写进 oplog 的条数

diff 的记法

实测 · 本机执行输出
  $set: {a: 2}                              → o = {'$v': 2, 'diff': {'u': {'a': 2}}}
  $set: {b: "y", a: 3}                      → o = {'$v': 2, 'diff': {'u': {'a': 3, 'b': 'y'}}}
  $unset: {keep: ""}                        → o = {'$v': 2, 'diff': {'d': {'keep': False}}}
  $inc: {a: 10}                             → o = {'$v': 2, 'diff': {'u': {'a': 13}}}
  $set: {brand_new: 1}(新字段)                 → o = {'$v': 2, 'diff': {'i': {'brand_new': 1}}}
  $rename: {brand_new: "renamed"}           → o = {'$v': 2, 'diff': {'d': {'brand_new': False}, 'i': {'renamed': 1}}}

update 条目的 o 带 $v: 2 和 diff:u 是新值,d 是删除的字段,i 是新增的字段,s<字段> 对子文档或数组递归。

记结果值

实测 · 本机执行输出
  $set: {"n.p": 2}(子文档里的字段)                 → o = {'$v': 2, 'diff': {'sn': {'u': {'p': 2}}}}
  $set: {"n.q.r": 6}(两层嵌套)                  → o = {'$v': 2, 'diff': {'sn': {'sq': {'u': {'r': 6}}}}}
  $set: {"n.p": 3, "a": 4}(同时改子文档和顶层)       → o = {'$v': 2, 'diff': {'u': {'a': 4}, 'sn': {'u': {'p': 3}}}}

$inc 记录的是结果值(13),不是增量(10)。

子文档与数组

实测 · 本机执行输出
  $set: {"arr.1": 20}                       → o = {'$v': 2, 'diff': {'sarr': {'a': True, 'u1': 20}}}
  $push: {arr: 4}                           → o = {'$v': 2, 'diff': {'sarr': {'a': True, 'u3': 4}}}
  $push: {tags: {$each: ["c", "d"]}}        → o = {'$v': 2, 'diff': {'stags': {'a': True, 'u2': 'c', 'u3': 'd'}}}
  $pop: {arr: 1}(删最后一个)                     → o = {'$v': 2, 'diff': {'u': {'arr': [1, 20, 3]}}}
  $pull: {tags: "a"}(从头部删)                  → o = {'$v': 2, 'diff': {'u': {'tags': ['b', 'c', 'd']}}}

嵌套路径逐层 s;数组改元素或追加用 a: true 加 u<下标>;$pop 与 $pull 记整个数组。

大小

实测 · 本机执行输出
  文档约 100,031 字节。$inc 一个字段:oplog 条目 o = {'$v': 2, 'diff': {'u': {'v': 1}}},整个条目 165 字节
  replace_one:oplog 条目 o 的键 ['_id', 'pad', 'v']…,整个条目 100,157 字节(存的是整个新文档)

只改一个字段,100 KB 文档的 oplog 条目 165 字节;整个替换要 100,157 字节,复制和 oplog 窗口都受影响。

没变化与批量

实测 · 本机执行输出
  把 a 设成它已有的值 4:matched 1 modified 0;oplog 新增 0 条
  update_many 修改 5 个文档:oplog 新增 5 条,各自的 o: [{'u': {'v': 1}}, {'u': {'v': 1}}, {'u': {'v': 1}}] …(每个文档一条,o2 是 [{'_id': 0}, {'_id': 1}, {'_id': 2}, {'_id': 3}, {'_id': 4}])

没有实际修改的 update 不写 oplog;update_many 每个被修改的文档一条。

管道 update

实测 · 本机执行输出
  [{$set: {a: {$add: ["$a", 1]}}}]          → o = {'$v': 2, 'diff': {'u': {'a': 5}}}
  [{$unset: "b"}]                           → o = {'$v': 2, 'diff': {'d': {'b': False}}}
  [{$replaceWith: {x: 1, y: 2}}]            → o = {'_id': 1, 'x': 1, 'y': 2}
  上一条的 o 的键: ['_id', 'x', 'y'](没有 diff,是整个新文档)

管道形式的 $set、$unset 同样是 diff,$replaceWith 换掉整个文档则整文档替换。

分步演示

oplog 里的 update diff
本步的对象

    本文用到的工具与边界

    • 本机 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、时间戳等每次运行都不同。单节点副本集;直接读 local.oplog.rs
    • 没有验证:关闭 diff 格式回到 $set 记法(想用隐藏参数 internalQueryEnableLoggingV2OplogEntries 关,在这个版本上该参数不存在);diff 在 secondary 上的应用过程;嵌套数组里子文档的 diff;diff 比整个文档更大时是否回退为整文档(没有构造出这种情形);change stream 的 updateDescription 与 oplog diff 的关系
    京ICP备2026067502号-1京公网安备11010502063487号