Navigation

Session.startTransaction()

On this page

Definition

Session.startTransaction(<options>)

New in version 4.0.

Starts a multi-document transaction associated with the session. At any given time, you can have at most one open transaction for a session.

Availability

  • In version 4.0, MongoDB supports multi-document transactions on replica sets.
  • In version 4.2, MongoDB introduces distributed transactions, which adds support for multi-document transactions on sharded clusters and incorporates the existing support for multi-document transactions on replica sets.

Important

Within a transaction, you can only specify read and write (CRUD) operations on existing collections. For example, a multi-document transaction cannot include an insert operation that would result in the creation of a new collection.

The Session.startTransaction() method can take a document following options:

{ readConcern: { level: <level>}, writeConcern: { w: <value>, j: <boolean>, wtimeout: <number> } }
Option Description
readConcern

Optional. A document that specifies the read concern for all operations in the transaction, overriding operation-specific read concern.

You can specify one of the following read concern levels:

For "local" and "majority" read concern, MongoDB may sometimes substitute a stronger read concern.

writeConcern

Optional. A document that specifies the write concern for the transaction. This write concern applies to the transaction commit and abort operations.

The operations within the transaction use "w: 1", overriding operation-specific write concern.

If you commit using "w: 1" write concern, your transaction can be rolled back during the failover process.

For MongoDB Drivers, transactions use the client-level write concern as the default.

Behavior

Operations Supported within a Transaction

Note

If running with access control, you must have privileges for the operations in the transaction.

For multi-document transactions:

  • You can specify read/write (CRUD) operations on existing collections. The collections can be in different databases. For a list of CRUD operations, see CRUD Operations.
  • You cannot write to capped collections. (Starting in MongoDB 4.2)
  • You cannot read/write to collections in the config, admin, or local databases.
  • You cannot write to system.* collections.
  • You cannot return the supported operation’s query plan (i.e. explain).
  • For cursors created outside of a transaction, you cannot call getMore inside the transaction.
  • For cursors created in a transaction, you cannot call getMore outside the transaction.
  • Starting in MongoDB 4.2, you cannot specify killCursors as the first operation in a transaction.
Method Command Note
db.collection.aggregate() aggregate

Excluding the following stages:

db.collection.countDocuments()  

Excluding the following query operator expressions:

The method uses the $match aggregation stage for the query and $group aggregation stage with a $sum expression to perform the count.

db.collection.distinct() distinct

Available on unsharded collections.

For sharded collections, use the aggregation pipeline with the $group stage. See Distinct Operation.
db.collection.find() find  
  geoSearch  
delete  
findAndModify For upsert, only when run against an existing collection.
insert Only when run against an existing collection.
db.collection.save()   If an insert, only when run against an existing collection.
update For upsert, only when run against an existing collection.
 
For insert operations, only when run against an existing collection.
For upsert, only when run against an existing collection.

Operations that affect the database catalog, such as creating or dropping a collection or an index, are not allowed in multi-document transactions. For example, a multi-document transaction cannot include an insert operation that would result in the creation of a new collection. See Restricted Operations.

Informational commands, such as isMaster, buildInfo, connectionStatus (and their helper methods) are allowed in transactions; however, they cannot be the first operation in the transaction.

Read Preference

Transactions support read preference primary.

Atomicity

While the transaction is open, no data changes made by operations in the transaction is visible outside the transaction:

  • When a transaction commits, all data changes made in the transaction are saved and visible outside the transaction. That is, a transaction will not commit some of its changes while rolling back others.

    Until a transaction commits, the data changes made in the transaction are not visible outside the transaction.

    However, when a transaction writes to multiple shards, not all outside read operations need to wait for the result of the committed transaction to be visible across the shards. For example, if a transaction is committed and write 1 is visible on shard A but write 2 is not yet visible on shard B, an outside read at read concern "local" can read the results of write 1 without seeing write 2.

  • When a transaction aborts, all data changes made by the writes in the transaction are discarded without ever becoming visible and the transaction ends.

Example

Consider a scenario where as changes are made to an employee’s record in the hr database, you want to ensure that the events collection in the reporting database are in sync with the hr changes. That is, you want to ensure that these writes are done as a single transaction, such that either both operations succeed or fail.

The employees collection in the hr database has the following documents:

{ "_id" : ObjectId("5af0776263426f87dd69319a"), "employee" : 3, "name" : { "title" : "Mr.", "name" : "Iba Ochs" }, "status" : "Active", "department" : "ABC" }
{ "_id" : ObjectId("5af0776263426f87dd693198"), "employee" : 1, "name" : { "title" : "Miss", "name" : "Ann Thrope" }, "status" : "Active", "department" : "ABC" }
{ "_id" : ObjectId("5af0776263426f87dd693199"), "employee" : 2, "name" : { "title" : "Mrs.", "name" : "Eppie Delta" }, "status" : "Active", "department" : "XYZ" }

The events collection in the reporting database has the following documents:

{ "_id" : ObjectId("5af07daa051d92f02462644a"), "employee" : 1, "status" : { "new" : "Active", "old" : null }, "department" : { "new" : "ABC", "old" : null } }
{ "_id" : ObjectId("5af07daa051d92f02462644b"), "employee" : 2, "status" : { "new" : "Active", "old" : null }, "department" : { "new" : "XYZ", "old" : null } }
{ "_id" : ObjectId("5af07daa051d92f02462644c"), "employee" : 3, "status" : { "new" : "Active", "old" : null }, "department" : { "new" : "ABC", "old" : null } }

The following example opens a transaction, updates an employee’s status to Inactive in the employees status and inserts a corresponding document to the events collection, and commits the two operations as a single transaction.

// Runs the txnFunc and retries if TransientTransactionError encountered

function runTransactionWithRetry(txnFunc, session) {
    while (true) {
        try {
            txnFunc(session);  // performs transaction
            break;
        } catch (error) {
            // If transient error, retry the whole transaction
            if ( error.hasOwnProperty("errorLabels") && error.errorLabels.includes("TransientTransactionError")  ) {
                print("TransientTransactionError, retrying transaction ...");
                continue;
            } else {
                throw error;
            }
        }
    }
}

// Retries commit if UnknownTransactionCommitResult encountered

function commitWithRetry(session) {
    while (true) {
        try {
            session.commitTransaction(); // Uses write concern set at transaction start.
            print("Transaction committed.");
            break;
        } catch (error) {
            // Can retry commit
            if (error.hasOwnProperty("errorLabels") && error.errorLabels.includes("UnknownTransactionCommitResult") ) {
                print("UnknownTransactionCommitResult, retrying commit operation ...");
                continue;
            } else {
                print("Error during commit ...");
                throw error;
            }
       }
    }
}

// Updates two collections in a transactions

function updateEmployeeInfo(session) {
    employeesCollection = session.getDatabase("hr").employees;
    eventsCollection = session.getDatabase("reporting").events;

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

    try{
        employeesCollection.updateOne( { employee: 3 }, { $set: { status: "Inactive" } } );
        eventsCollection.insertOne( { employee: 3, status: { new: "Inactive", old: "Active" } } );
    } catch (error) {
        print("Caught exception during transaction, aborting.");
        session.abortTransaction();
        throw error;
    }

    commitWithRetry(session);
}

// Start a session.
session = db.getMongo().startSession( { readPreference: { mode: "primary" } } );

try{
   runTransactionWithRetry(updateEmployeeInfo, session);
} catch (error) {
   // Do something with error
} finally {
   session.endSession();
}