bigywater.tech
MongoDB · 55

多文档事务:快照隔离与 WriteConflict

多文档事务到底是什么隔离级别?写冲突怎么表现?并发下怎么重试?快照隔离能不能保证业务规则?这篇在副本集上逐个实验。

code 112
两个事务写同一个文档时后者的 WriteConflict 错误码,4 ms 就返回
1,492 次
16 线程在一个热点文档上做 1600 次事务加一,因冲突额外重试的次数
在岗 0 人
写偏斜:两个事务各让一个医生下班后实际的在岗人数

快照

实测 · 本机执行输出
  事务 T1 开始后读 A.bal = 100
  事务外把 A.bal 改成 150 并提交;事务外读 = 150;T1 里再读 = 100(仍是快照里的值)
  T1 提交后,在新的读里 A.bal = 150

事务开始后读到的是那一刻的快照,别人之后提交的修改在事务结束前看不到。

写冲突

实测 · 本机执行输出
  T2 的更新:4 ms 后报错 code=112(WriteConflict),errorLabels=['TransientTransactionError']

两个事务修改同一个文档,后来的直接失败:code 112 WriteConflict,带 TransientTransactionError 标签,表示可以整个重试。

事务外的写

实测 · 本机执行输出
  T1 仍未提交 1.5 秒后,事务外的 update 完成了吗: False
  T1 提交后事务外的 update 才完成:总共等了 1526 ms;最终 A.bal = 201(100 + 1 + 100)

事务外的写入遇到事务里未提交的修改会等待,不会报错,直到事务提交或中止。

重试

实测 · 本机执行输出
  最终 A.bal = 1600(应为 1600);耗时 2.12 s;因 WriteConflict 额外重试了 1492 次(平均每次提交 0.93 次)

with_transaction 遇到临时性错误会重试整个回调;热点文档上平均每次提交约一次重试,结果仍然正确。

写偏斜

实测 · 本机执行输出
  两个事务各自看到的在岗人数 [2, 2],都让自己下班了;事务结束后在岗人数 = 0(规则被破坏)

快照隔离只检测对同一文档的写冲突,检查一个条件后更新另一个文档,会出现写偏斜,业务不变式可能被破坏。

限制

实测 · 本机执行输出
  count 命令:code=263 Cannot run 'count' in a multi-document transaction. Please see http://dochub.mongodb.org/core/transa
  新开一个事务:countDocuments = 2,find_one 的结果 {'_id': 'B', 'bal': 100}

事务里不能用 count 命令,要用 countDocuments。

分步演示

多文档事务
本步的对象

    本文用到的工具与边界

    • 本机 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、时间戳等每次运行都不同。副本集只有 1 个节点(足够用事务);并发线程数与重试次数每次运行都不同
    • 没有验证:readConcern local / majority 的事务;事务的 writeConcern 在 commit 时的作用;分片集群上的分布式事务;长事务对 WiredTiger 缓存(history store)的影响;事务内创建集合与索引的限制;"读已提交"在事务外的表现(见读关注一篇)
    京ICP备2026067502号-1京公网安备11010502063487号