MongoDB 事务

事务基础(mongosh)

多文档事务需要副本集或分片集群,自 MongoDB 4.0 起可用。

// Start a session and transaction
const session = db.getMongo().startSession();
session.startTransaction({
  readConcern:  { level: "snapshot" },
  writeConcern: { w: "majority" }
});

try {
  const orders   = session.getDatabase("shop").orders;
  const inventory = session.getDatabase("shop").inventory;

  orders.insertOne(
    { userId: "u1", productId: "p1", qty: 2, total: 49.98 },
    { session }
  );
  inventory.updateOne(
    { productId: "p1" },
    { $inc: { stock: -2 } },
    { session }
  );

  session.commitTransaction();
  print("Transaction committed");
} catch (err) {
  session.abortTransaction();
  print("Transaction aborted:", err);
} finally {
  session.endSession();
}

Node.js 驱动示例

// Using withTransaction() helper (auto-retry on transient errors)
const { MongoClient } = require("mongodb");
const client = new MongoClient(uri);

async function transferFunds(fromId, toId, amount) {
  const session = client.startSession();
  try {
    await session.withTransaction(async () => {
      const accounts = client.db("bank").collection("accounts");

      const from = await accounts.findOne({ _id: fromId }, { session });
      if (from.balance < amount) throw new Error("Insufficient funds");

      await accounts.updateOne(
        { _id: fromId },
        { $inc: { balance: -amount } },
        { session }
      );
      await accounts.updateOne(
        { _id: toId },
        { $inc: { balance: amount } },
        { session }
      );
    }, {
      readConcern:  { level: "snapshot" },
      writeConcern: { w: "majority" },
      readPreference: "primary"
    });
  } finally {
    await session.endSession();
  }
}

可重试写入

可重试写入在网络错误和主节点故障转移时自动重试一次,驱动 3.6+ 版本默认启用。

// Enable in connection string (default in modern drivers)
const client = new MongoClient("mongodb://host:27017/db?retryWrites=true");

// Retryable writes work for:
// - insertOne, insertMany
// - updateOne, replaceOne
// - deleteOne
// - findOneAndUpdate, findOneAndReplace, findOneAndDelete
// - bulkWrite (ordered and unordered)

// NOT retryable: multi-document writes (updateMany, deleteMany)
// NOT retryable: operations inside transactions (managed separately)

// Disable retryable writes (e.g., for legacy compatibility)
const client2 = new MongoClient(uri, { retryWrites: false });

因果一致性

因果一致性确保会话读取自己的写入,即使针对从节点也是如此。

// Causally consistent session
const session = client.startSession({ causalConsistency: true });

// Write to primary
await db.collection("settings").updateOne(
  { key: "theme" },
  { $set: { value: "dark" } },
  { session, writeConcern: { w: "majority" } }
);

// Read from secondary — guaranteed to see the write above
await db.collection("settings").findOne(
  { key: "theme" },
  {
    session,
    readPreference: "secondary",
    readConcern: { level: "majority" }
  }
);

事务限制与最佳实践

限制默认值/建议
最大事务时长60 seconds (transactionLifetimeLimitSeconds)
最大文档锁数量无硬性限制,但应保持少量
Oplog 条目大小限制16 MB per transaction
事务中建集合MongoDB 4.4 起支持
保持事务简短减少锁争用和 WiredTiger 冲突
避免长时间运行的查询事务内优先使用预计算或索引查找