阿里云主机折上折
  • 微信号
Current Site:Index > ACID transaction support (single-document and multi-document transactions)

ACID transaction support (single-document and multi-document transactions)

Author:Chuan Chen 阅读数:48656人阅读 分类: MongoDB

Basic Concepts of ACID Transactions

ACID refers to the four key properties of database transactions: Atomicity, Consistency, Isolation, and Durability. In MongoDB, transaction support has evolved from single-document to multi-document capabilities. Prior to version 4.0, MongoDB guaranteed ACID properties only at the single-document level. Starting with version 4.0, MongoDB introduced multi-document transaction support, enabling cross-collection and cross-document operations to also meet ACID requirements.

// Single-document transaction example
db.products.updateOne(
  { _id: 1, stock: { $gte: 1 } },
  { $inc: { stock: -1 }, $set: { lastModified: new Date() } }
)

Implementation of Single-Document Transactions

MongoDB inherently supports ACID properties at the single-document level. When performing a write operation on a single document, MongoDB ensures the operation either fully succeeds or completely fails, with no partial updates. This atomicity is achieved through document-level locking mechanisms.

Typical use cases for single-document transactions include:

  • Updating multiple fields within a document
  • Adding or removing elements from an array
  • Modifying the contents of nested documents
// Complex single-document transaction example
db.accounts.updateOne(
  { _id: "account1" },
  {
    $inc: { balance: -100 },
    $push: { transactions: { amount: 100, date: new Date(), type: "withdrawal" } }
  }
)

Evolution of Multi-Document Transactions

MongoDB 4.0 introduced support for multi-document ACID transactions, initially only for replica set deployments. Version 4.2 extended this to sharded clusters. Multi-document transactions allow operations across multiple documents, collections, or even databases within a single transaction.

Key features of multi-document transactions:

  • Support for read preference and write concern
  • Default maximum transaction duration of 60 seconds (configurable)
  • Default isolation level of snapshot isolation
// Multi-document transaction example
const session = db.getMongo().startSession();
session.startTransaction({
  readConcern: { level: "snapshot" },
  writeConcern: { w: "majority" }
});

try {
  const accounts = session.getDatabase("bank").accounts;
  accounts.updateOne({ _id: "A" }, { $inc: { balance: -100 } });
  accounts.updateOne({ _id: "B" }, { $inc: { balance: 100 } });
  
  session.commitTransaction();
} catch (error) {
  session.abortTransaction();
  throw error;
} finally {
  session.endSession();
}

Performance Considerations for Transactions

Using multi-document transactions incurs certain performance overheads, primarily:

  1. Increased system resource usage during transactions
  2. Need to maintain transaction state and logs
  3. Potential for lock contention

Best practice recommendations:

  • Keep transactions as brief as possible
  • Avoid time-consuming operations within transactions
  • Set appropriate transaction timeout values
  • Consider using retryable writes for simple transactions
// Transaction example with retry logic
function runTransactionWithRetry(txnFunc, session) {
  while (true) {
    try {
      txnFunc(session);
      break;
    } catch (error) {
      if (error.hasErrorLabel("TransientTransactionError")) {
        continue;
      }
      throw error;
    }
  }
}

Transaction Isolation Levels

MongoDB provides two primary transaction isolation levels:

  1. Read uncommitted - Default level
  2. Snapshot isolation - Achieved by explicitly setting readConcern

Isolation level choices affect:

  • Data consistency
  • Concurrent performance
  • Likelihood of dirty reads or phantom reads
// Transaction with snapshot isolation level
const session = db.getMongo().startSession();
session.startTransaction({
  readConcern: { level: "snapshot" },
  writeConcern: { w: "majority" }
});

Transactions in Replica Sets and Sharded Clusters

In replica set environments, transaction behavior is relatively straightforward. However, in sharded clusters, transactions involve greater complexity:

  1. All shards participating in a transaction must belong to the same replica set
  2. Transactions cannot span multiple shard key ranges
  3. A coordinator is required to manage cross-shard transactions

Limitations of sharded cluster transactions:

  • Cannot affect more than 1,000 documents
  • Cannot run longer than the operation execution time limit
  • Certain DDL operations cannot be performed within transactions
// Transaction example in a sharded cluster
const session = db.getMongo().startSession();
session.startTransaction();

try {
  // Operations must be on the same shard
  db.orders.insertOne({ _id: 1, item: "abc", price: 100 }, { session });
  db.inventory.updateOne(
    { item: "abc", qty: { $gte: 1 } },
    { $inc: { qty: -1 } },
    { session }
  );
  
  session.commitTransaction();
} catch (error) {
  session.abortTransaction();
  throw error;
}

Transaction Error Handling

Proper handling of transaction errors is crucial for maintaining data consistency. Common transaction errors include:

  • TransientTransactionError: Temporary error, usually retryable
  • UnknownTransactionCommitResult: Commit outcome unknown
  • NoSuchTransaction: Transaction does not exist
// Complete transaction error handling example
async function transferFunds(from, to, amount) {
  const session = client.startSession();
  
  try {
    await session.withTransaction(async () => {
      const fromAccount = await accounts.findOne({ _id: from }, { session });
      if (fromAccount.balance < amount) {
        throw new Error("Insufficient funds");
      }
      
      await accounts.updateOne(
        { _id: from },
        { $inc: { balance: -amount } },
        { session }
      );
      
      await accounts.updateOne(
        { _id: to },
        { $inc: { balance: amount } },
        { session }
      );
    });
  } catch (error) {
    if (error.errorLabels && error.errorLabels.includes("TransientTransactionError")) {
      console.log("Transient error, retrying...");
      return transferFunds(from, to, amount);
    }
    throw error;
  } finally {
    await session.endSession();
  }
}

Transaction Monitoring and Diagnostics

MongoDB provides various tools for monitoring and analyzing transaction performance:

  1. Database commands:

    • currentOp: View running transactions
    • serverStatus: Obtain transaction statistics
  2. Log analysis:

    • Transaction start/commit/abort records
    • Lock acquisition and release information
  3. Performance metrics:

    • Transaction duration
    • Transaction retry count
    • Conflict rate
// View currently running transactions
db.adminCommand({
  currentOp: true,
  $or: [
    { op: "command", "command.abortTransaction": { $exists: true } },
    { op: "command", "command.commitTransaction": { $exists: true } },
    { op: "command", "command.startTransaction": { $exists: true } }
  ]
})

Transaction Best Practices

Based on production environment experience, consider the following best practices when using MongoDB transactions:

  1. Schema design:

    • Prefer embedded documents to minimize cross-document operations
    • Consider using two-phase commit patterns for complex scenarios
  2. Performance optimization:

    • Place frequently updated fields at the beginning of documents
    • Avoid collection scans within transactions
  3. Application layer handling:

    • Implement retry logic for transient errors
    • Set reasonable timeout values
// Two-phase commit pattern example
const transaction = {
  _id: "txn123",
  state: "initial",
  participants: [
    { resource: "accounts", id: "A", action: "withdraw", amount: 100 },
    { resource: "accounts", id: "B", action: "deposit", amount: 100 }
  ],
  lastModified: new Date()
};

// Phase 1: Prepare
db.transactions.insertOne(transaction);
db.accounts.updateOne(
  { _id: "A", pendingTransactions: { $ne: "txn123" } },
  { $inc: { balance: -100 }, $push: { pendingTransactions: "txn123" } }
);

// Phase 2: Commit
db.transactions.updateOne(
  { _id: "txn123", state: "initial" },
  { $set: { state: "committed" } }
);
db.accounts.updateOne(
  { _id: "A" },
  { $pull: { pendingTransactions: "txn123" } }
);

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.