mongodb事务必须运行在副本集或分片集群上,本地单节点需启动--replset并执行rs.initiate();连接字符串须含?replicaset=rs0;事务内所有db操作必须await、禁止try/catch吞错、读取需snapshot级别、关键更新须带条件、禁用外部i/o、应设maxtimems防长事务。

事务必须跑在 replica set 上,否则 session.withTransaction() 直接报错
本地开发时用 mongod --port 27017 启动单节点服务,调用 session.withTransaction() 会立刻失败,错误信息是:Transaction numbers are only allowed on a replica set member or mongos。这不是代码问题,是部署前提没满足。
- 本地验证最快方式:启动带
--replSet rs0的实例,例如mongod --replSet rs0 --port 27017 --dbpath /data/db,连上后执行rs.initiate() - 连接字符串里必须加
?replicaSet=rs0,否则驱动无法绑定事务上下文 - Sharded cluster 下事务只能跨分片操作同个数据库内的集合,不能跨库——对账逻辑涉及多个集合(如
accounts、transactions、reconciliation_logs)时,必须全放在一个 DB 里
withTransaction() 回调里必须 await 所有 DB 操作
Node.js 中,如果回调函数里漏掉 await,比如写成 coll.updateOne(..., { session }) 而不是 await coll.updateOne(..., { session }),事务会提前结束,后续操作在事务外执行,原子性彻底失效。
- 所有数据库调用(
updateOne、insertOne、findOne)都必须显式await - 不能在回调里
try/catch吞掉错误并return,否则驱动认为成功并提交——比如余额校验失败后return,扣款就真发生了 - 对账逻辑常需“先查再判再改”,例如查上期未平账项 → 计算差异 → 插入对账结果 → 更新账户状态,每一步都要
await并让错误自然抛出
并发场景下必须用条件更新 + snapshot 读关注,否则出现脏读或覆盖
财务对账不是简单加减,而是依赖历史快照做比对。若用默认 local 读关注,可能读到其他事务未提交的中间状态;若不加条件更新,两个对账任务同时读到同一笔未处理流水,都会标记为“已核销”,导致重复处理。
- 事务内所有读操作必须设
readConcern: { level: 'snapshot' },确保读取的是事务开始时刻的一致快照 - 关键更新(如标记流水为已对账)必须带条件,例如
updateOne({ _id: id, status: 'pending' }, { $set: { status: 'reconciled' } }, { session }),避免并发覆盖 - 不要在事务里做 HTTP 请求或文件写入——它们无法回滚,一旦 DB 提交但外部系统失败,账就对不上
超时和长事务会锁死文档,对账脚本要主动控制生命周期
MongoDB 默认事务最大运行时间是 60 秒(transactionLifetimeLimitSeconds),但更危险的是人为卡住:比如在事务里加了 console.log() 然后断点调试,或等待人工确认输入。此时相关文档被写锁持有,其他对账或转账请求全部排队,严重时引发级联超时。
- 对账逻辑若涉及大量数据扫描,应拆成多个小事务(例如按日期分批),而不是单个长事务
- 在
withTransaction()的 options 里显式设maxTimeMS: 30000,强制中断异常长的操作 - 避免在事务内做任何阻塞 I/O,包括日志输出、定时等待、外部 API 调用











