首页 / MongoDB 入门教程 / 多文档事务

MongoDB 入门教程

多文档事务

本教程共 50 篇 · 第 49 篇 · 更新于 2026-07-30 · 约 4 分钟阅读

MongoDBMongoDB 入门教程多文档事务事务ACID副本集

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 小结

多文档事务用会话串起 startTransactioncommitTransactionabortTransaction,保证跨集合写入全成或全回。它需要副本集/分片集群,满足 ACID,但开销大于单文档写入。优先用嵌入建模,事务作为最后手段。