0Pricing
MongoDB Academy · 课时

启动会话和多文档事务

您将打开 ClientSession,在 startTransaction() 中执行多个操作,并提交或中止事务。

启动会话和多文档事务 是 CoddyKit 上的免费 MongoDB Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 MongoDB Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 MongoDB Academy 课程共包含 4 节课。

本课时的部分内容尚未翻译,以英文显示。

Sessions Are the Foundation

MongoDB multi-document transactions require a session. A session is a server-side context that tracks your causally consistent reads and transaction state. You create a session from the MongoClient, pass it to every operation inside the transaction, and then end the session when you are done. Forgetting to pass the session object means operations run outside the transaction and are not rolled back on abort.

Creating a Session With startSession()

Call client.startSession() to obtain a ClientSession object. This does not start a transaction yet—it only establishes the server-side context. Sessions can optionally be configured for causal consistency so that reads within the session always reflect all prior writes in the same session, even on secondaries. Always end the session in a finally block to release server resources.

const { MongoClient } = require('mongodb');
const client = new MongoClient(process.env.MONGODB_URI);

async function run() {
  const session = client.startSession();
  try {
    // ... use session here
  } finally {
    await session.endSession();
    await client.close();
  }
}

Starting a Transaction

Call session.startTransaction() with optional transaction options to begin a multi-document transaction. Common options include readConcern (typically 'snapshot' for full isolation) and writeConcern (typically { w: 'majority' } for durable commits). Once started, all operations that pass this session are part of the transaction and will be rolled back if the transaction aborts.

session.startTransaction({
  readConcern: { level: 'snapshot' },
  writeConcern: { w: 'majority' }
});

Passing the Session to Operations

Every operation you want to include in the transaction must receive the session object as an option. If you forget to pass the session to an operation, it runs outside the transaction with its own independent write that will not be rolled back on abort. This is a common source of bugs in transaction code—always double-check that every insert, update, and delete inside the try block receives the session.

const db = client.db('bank');
const accounts = db.collection('accounts');

// Both operations must receive { session } to be part of the transaction
await accounts.updateOne(
  { _id: fromAccountId },
  { $inc: { balance: -transferAmount } },
  { session }  // <-- REQUIRED
);

await accounts.updateOne(
  { _id: toAccountId },
  { $inc: { balance: transferAmount } },
  { session }  // <-- REQUIRED
);

Committing With commitTransaction()

After all operations complete successfully, call session.commitTransaction() to atomically apply all changes to the database. Until commit is called, none of the transaction's writes are visible to other operations. On commit, MongoDB applies all writes and acknowledges according to the write concern. A successful commit means all operations in the transaction are durably saved.

session.startTransaction();
try {
  await accounts.updateOne({ _id: fromId }, { $inc: { balance: -100 } }, { session });
  await accounts.updateOne({ _id: toId }, { $inc: { balance: 100 } }, { session });
  await session.commitTransaction();
  console.log('Transfer committed successfully');
} catch (error) {
  await session.abortTransaction();
  throw error;
}

Aborting With abortTransaction()

If any operation in the transaction fails or if your application logic determines the transaction should not proceed, call session.abortTransaction(). This rolls back all writes made in the transaction as if none of them happened. MongoDB guarantees that no partial state will be left—other readers will never see any of the aborted transaction's writes.

session.startTransaction();
try {
  const inventory = await db.collection('inventory').findOne({ _id: itemId }, { session });
  
  if (inventory.stock < requestedQty) {
    // Business logic: not enough stock — abort
    await session.abortTransaction();
    return { success: false, reason: 'Insufficient stock' };
  }
  
  await db.collection('inventory').updateOne(
    { _id: itemId }, { $inc: { stock: -requestedQty } }, { session }
  );
  await db.collection('orders').insertOne({ itemId, qty: requestedQty, status: 'confirmed' }, { session });
  await session.commitTransaction();
  return { success: true };
} catch (error) {
  await session.abortTransaction();
  throw error;
}

The withTransaction() Helper

The Node.js driver provides a convenient session.withTransaction(fn) helper that automatically handles starting, committing, and aborting the transaction, including automatic retries for transient errors. The callback function receives the session and should contain all your transaction operations. Using withTransaction is recommended over manual start/commit/abort because it correctly handles the retry logic MongoDB requires.

const session = client.startSession();
try {
  await session.withTransaction(async () => {
    await accounts.updateOne({ _id: fromId }, { $inc: { balance: -100 } }, { session });
    await accounts.updateOne({ _id: toId }, { $inc: { balance: 100 } }, { session });
    // withTransaction auto-commits on success, auto-aborts on error, and retries transient errors
  }, {
    readConcern: { level: 'snapshot' },
    writeConcern: { w: 'majority' }
  });
} finally {
  await session.endSession();
}

Transaction Scope and Collections

A MongoDB transaction can span multiple collections and databases within the same cluster (MongoDB 4.2+ for sharded clusters). You can read from one collection, update another, and insert into a third—all within a single atomic transaction. The only restriction is that you cannot create new collections or indexes inside a multi-document transaction; those DDL operations must happen outside transactions.

await session.withTransaction(async () => {
  const db = client.db('ecommerce');
  
  // Span multiple collections in one transaction
  await db.collection('inventory').updateOne(
    { productId: 'P1' }, { $inc: { stock: -qty } }, { session }
  );
  await db.collection('orders').insertOne(
    { productId: 'P1', qty, status: 'new', createdAt: new Date() }, { session }
  );
  await db.collection('customers').updateOne(
    { _id: customerId }, { $push: { orderHistory: orderId } }, { session }
  );
});

Transactions Require Replica Sets

Multi-document transactions require a replica set or sharded cluster—they do not work on a standalone MongoDB instance. On a standalone, the transaction API exists but calling startTransaction() throws an error. This means your local development setup should use a local replica set (e.g., via mongod --replSet rs0 or via Atlas free tier) if your application code uses transactions.

// Starting a local replica set for development:
// 1. Start mongod with replica set name
// mongod --replSet rs0 --port 27017 --dbpath /data/db

// 2. In mongosh, initiate the replica set:
// rs.initiate()

// Now transactions will work on localhost

Transaction Timeout and Limits

MongoDB enforces a 60-second maximum transaction lifetime by default (configurable via transactionLifetimeLimitSeconds). Transactions that run longer are automatically aborted. Additionally, transactions are capped to 16 MB of oplog space for write operations. Long-running transactions also hold locks and can degrade performance for concurrent operations, so keep transactions short and focused.

Full Transfer Example With Validation

Putting it all together: a complete, production-ready bank transfer function using withTransaction. It validates sufficient balance inside the transaction (ensuring no other writer could have drained the account between the check and the debit) and records an audit entry atomically. This pattern demonstrates all key transaction concepts.

async function transferFunds(client, 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 || 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 });
      await client.db('bank').collection('auditLog').insertOne(
        { from: fromId, to: toId, amount, date: new Date(), type: 'transfer' }, { session }
      );
    });
    return { success: true };
  } finally {
    await session.endSession();
  }
}

Quick Check

Test your understanding of MongoDB & NoSQL Databases concepts from this lesson.

Lesson Recap

In this lesson you learned: transactions require a ClientSession created with client.startSession(), every operation in the transaction must receive { session } to be included, and withTransaction() is the recommended helper because it handles retry logic and cleanup automatically. Next up we explore error handling and retry logic for transient transaction failures.

常见问题解答

「启动会话和多文档事务」课时是免费的吗?

是的 — 「启动会话和多文档事务」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 MongoDB Academy 课程的其余内容,请升级到 CoddyKit PRO。 MongoDB Academy 课程共包含 4 节课。

「启动会话和多文档事务」这节课中我会学到什么?

您将打开 ClientSession,在 startTransaction() 中执行多个操作,并提交或中止事务。 你通过在浏览器中直接运行的动手代码来练习 MongoDB Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 MongoDB Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 MongoDB Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「启动会话和多文档事务」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 MongoDB Academy 课中编写并运行代码吗?

能。每节 MongoDB Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 分布式文档存储中的 ACID 保证
  2. 启动会话和多文档事务
  3. 错误处理和重试逻辑
  4. 事务性能考量
← 返回 MongoDB Academy