必须使用MongoDB 4.0+服务端与Node.js驱动v4.0+(推荐v7.4),并确保复制集配置、显式启动事务、所有操作传入session选项;提交/回滚及endSession均需手动处理,且session不可重用。

必须用 MongoDB 4.0+ 服务端 + Node.js 驱动 v4.0+(推荐 v7.4),否则 session.startTransaction() 直接报错或静默失效。
事务前提:服务端版本与驱动兼容性检查
Node.js 18 本身不决定事务能力,关键在 MongoDB 服务端和驱动版本匹配:
- MongoDB Server 必须 ≥ 4.0(推荐 6.0+ 或 Atlas 最新版),4.2 已被 Node.js 驱动 v7.3+ 正式弃用;
- Node.js 驱动必须 ≥ v4.0(v7.4 是当前稳定主力,支持
await using自动清理); - 连接字符串中不能含
replicaSet参数却连单节点,否则事务会因缺少复制集而拒绝启动; - 本地测试时,用
mongod --replSet rs0启动并运行rs.initiate(),否则session.startTransaction()抛出"Transaction numbers are only allowed on a replica set member"。
正确开启事务的三步硬要求
漏掉任意一步,事务就退化为普通操作,不会回滚:
- 调用
client.startSession()获取ClientSession实例(不是每次操作都新建 session); - 在 session 上显式调用
session.startTransaction()(不调用 = 没事务); - 所有数据库操作(
updateOne、insertOne、deleteMany等)必须传入{ session }选项 —— 这是最容易漏的点,不传就脱离事务上下文。
示例关键片段:
使用 JSON Schema 验证 JSON 数据,从示例 JSON 生成 schema,并将其转换为 TypeScript 接口、Python 数据类或 Markdown 文档。
const session = client.startSession();
await session.startTransaction(); // 必须
await accounts.updateOne({ _id: 'A' }, { $inc: { balance: -100 } }, { session }); // 必须带 session
await transactions.insertOne({ from: 'A', to: 'B', amount: 100 }, { session }); // 同样必须
提交与回滚必须显式处理,且 session 必须手动结束
Node.js 驱动不会自动 commit/abort,也不会自动 endSession —— 不做这些,连接泄漏、事务卡住、后续操作阻塞都是常见后果:
- 成功路径:必须
await session.commitTransaction(),之后再session.endSession(); - 失败路径:必须
await session.abortTransaction(),再session.endSession(); - 推荐用
try/catch/finally结构,把session.endSession()放在finally块里; - v7.4+ 支持
await using session = client.startSession()(需启用--harmony-explicit-resource-management),可自动 endSession,但 commit/abort 仍需手动。
跨集合事务的隐含限制
看似能自由操作多个集合,实际有边界:
- 所有集合必须属于同一个数据库(
db.collection()调用的 db 实例必须一致),跨库事务不支持; - 不能在事务中创建新集合或新索引(
createCollection、createIndex会抛"This MongoDB deployment does not support retryable writes"类错误); - 读写操作必须在事务启动后、提交前完成;事务内执行
findOne查自己刚写的文档是安全的(因果一致性),但事务外查不到,直到 commit; - 事务默认有 60 秒超时(
maxTimeMS可设,但不能超过服务端transactionLifetimeLimitSeconds配置,默认 60)。
真正容易被忽略的是:事务失败后,session 对象不可重用。哪怕只 abort 了一次,这个 session 就废了,下次必须重新 startSession()。


















