多文档事务
本教程共 50 篇 · 第 49 篇 · 更新于 2026-07-30 · 约 4 分钟阅读
49. 多文档事务
本节目标:会用多文档事务保证跨集合写入的原子性,清楚它的部署前提和性能代价。
第 36 章说过:单文档写入天然原子。可一旦操作跨了多个文档、多个集合,就需要多文档事务(Multi-Document Transaction)来兜底。
49.1 为什么需要事务
设想一个下单流程:扣减 products 的库存,同时在 orders 建订单。这两步必须「同生共死」。
// 没有事务时,扣了库存、建订单却失败,库存就凭空少了
test> db.products.updateOne({ _id: 1 }, { $inc: { stock: -1 } })
test> db.orders.insertOne({ user_id: 1, items: [...] }) // 假如这里崩了
事务就是为了解决这种「半途而废」的问题。
49.2 前提:副本集或分片集群
多文档事务只能在副本集(Replica Set)或分片集群上用。单机 mongod 不支持。
Warning这是官方明确限制:事务需要副本集(FCV 至少 4.0)或分片集群(FCV 至少 4.2)。本地练习请先
rs.initiate()起副本集。
49.3 用会话跑一个事务
mongosh 里,事务挂在「会话(Session)」上。步骤是:开会话 → 开事务 → 操作 → 提交或回滚。
// 开会话并启动事务
test> const session = db.getMongo().startSession()
test> session.startTransaction()
try {
// 所有操作都带上 session,走同一事务
session.getDatabase("test").products.updateOne(
{ _id: 1 }, { $inc: { stock: -1 } }, { session }
)
session.getDatabase("test").orders.insertOne(
{ user_id: 1, items: [{ product: "机械键盘", qty: 1, price: 399 }], total: 399, status: "paid", created_at: new Date() },
{ session }
)
// 都成功才提交
session.commitTransaction()
print("下单成功,库存与订单一致")
} catch (e) {
// 任一步失败,整体回滚
session.abortTransaction()
print("事务回滚:" + e)
} finally {
session.endSession()
}
commitTransaction 让两步改动同时生效;abortTransaction 让它们全部撤销,库回到事务前的状态。
Tip关键点:事务里的每个写操作都要传
{ session },否则它跑在事务之外,不受保护。
49.4 ACID 边界
事务满足 ACID 四个性质:
- Atomicity 原子性:要么全做,要么全不做。
- Consistency 一致性:事务前后数据都满足既定规则。
- Isolation 隔离性:未提交前,外部读不到中间改动。
- Durability 持久性:提交后改动持久保存。
Note事务内的操作可以跨多个集合、多个库,甚至跨分片。这就是它被称作「分布式事务」的原因。
49.5 代价与建议
事务不是免费的。官方明确提醒:多文档事务比单文档写入开销大,不该拿它替代好的建模。
// 能用嵌入解决的,就别上事务
test> db.users.updateOne(
{ _id: 1 },
{ $set: { name: "小明", "profile.phone": "13900000000" } }
)
回顾建模篇:把相关数据嵌入同一文档,单文档更新天然原子,往往就不需要事务了。
Warning别把事务当万能药。能嵌入就嵌入,实在要跨文档再考虑事务。滥用事务会把简单问题复杂化、还拖慢系统。
49.6 小结
多文档事务用会话串起 startTransaction、commitTransaction、abortTransaction,保证跨集合写入全成或全回。它需要副本集/分片集群,满足 ACID,但开销大于单文档写入。优先用嵌入建模,事务作为最后手段。