MongoDB事务必须显式绑定会话:所有CRUD操作须通过session.db().collection()获取实例执行,禁止直接调用client.db().collection();跨数据库操作不支持;禁用createIndex等非事务命令;异常处理需含abortTransaction()和endSession()。

事务必须显式绑定到会话对象,不能直接在 client 上调用
很多开发者误以为 client.db().collection().updateOne() 这类操作能自动加入事务,其实完全不行。MongoDB 的事务机制要求所有参与事务的 CRUD 操作,都必须通过 session 对象获取的 collection 实例来执行。否则操作会脱离事务上下文,变成普通非事务写入。
正确做法是:先调用 client.startSession() 创建会话,再用 session.db("db").collection("coll") 获取 collection 实例;所有读写必须走这个实例。
- 错误示例:
db.collection("orders").insertOne(...)—— 不在事务中 - 正确示例:
session.db("mydb").collection("orders").insertOne(...) - 注意:
session本身不带数据库上下文,必须显式调用session.db()
事务内不能跨分片或跨数据库操作
MongoDB 分片集群上,事务只支持单一分片内的多集合操作;副本集则支持同一数据库下的任意集合。但无论哪种部署,都不允许事务同时写入 db1.coll1 和 db2.coll2 —— 即使两个数据库物理上在同一分片或副本集节点也不行。
常见踩坑点:
- 电商订单场景中,把
orders、inventory、users放在不同数据库里,结果事务启动失败,报错Transaction numbers are only allowed on a replica set member or a sharded cluster或更隐晦的cross-shard transaction not allowed - 解决办法:提前规划,将事务涉及的所有集合统一放在同一个数据库下(如全部归入
ecommerce库),并确保它们落在同一分片(可通过sh.addShardToZone+ 分片键控制) - 分片集群中,若集合未开启分片(即作为“未分片集合”存在),它会被路由到主分片,此时可安全参与事务 —— 但该集合不能是其他已分片集合的“同名但不同分片键”的副本
事务中避免使用不支持的操作,否则 commit 直接失败
MongoDB 明确禁止在事务中使用某些命令,比如 $out、$merge、mapReduce、distinct、createIndex 等。这些操作要么修改元数据,要么触发跨文档副作用,与事务的 snapshot 隔离模型冲突。
典型现象:
- 代码里写了
collection.aggregate([{$match:...}, {$out:"tmp_result"}]),事务执行到这步不报错,但后续session.commitTransaction()抛出CommandNotSupported: $out is not supported in transactions - 聚合中用了
$lookup关联其他集合 —— 只要被关联集合也在同一数据库且满足分片约束,是允许的;但若$lookup指向另一个数据库,就立刻失败 - 事务中执行
collection.createIndex({field: 1})会直接中断事务流程,且无法回滚已执行的写操作(因为索引创建不是事务性操作)
异常处理必须覆盖 abort + endSession,否则会话泄漏
事务失败后,仅调用 session.abortTransaction() 不够。MongoDB 会话资源不会自动释放,长期泄漏会导致连接数飙升、会话堆积、甚至触发 maxSessionTimeoutMinutes 强制终止引发不可预期行为。
务必在 finally 块中调用 session.endSession(),哪怕事务已成功提交:
const session = client.startSession();
try {
session.startTransaction();
await session.db("mydb").collection("orders").insertOne({...});
await session.db("mydb").collection("inventory").updateOne({...});
await session.commitTransaction();
} catch (error) {
await session.abortTransaction();
throw error;
} finally {
await session.endSession(); // 这行不能少
}
另外,Laravel 的 laravel-mongodb 包明确不支持嵌套事务,如果业务逻辑里出现递归或间接调用事务方法,会直接抛 RuntimeException;这种场景建议改用补偿逻辑(Saga 模式)或预校验+单事务重试。











