************ Transactions ************ .. default-domain:: mongodb .. contents:: On this page :local: :backlinks: none :depth: 1 :class: singlecol Version 4.0 of the MongoDB server introduces `multi-document transactions `_. (Updates to multiple fields within a single document are atomic in all versions of MongoDB.) Ruby driver version 2.6.0 adds support for transactions. .. _using-transactions: Using Transactions ================== In order to start a transaction, the application must have a :ref:`session `. The recommended way to use transactions is to utilize the ``with_transaction`` helper method: .. code-block:: ruby session = client.start_session session.with_transaction do collection.insert_one({hello: 'world'}, session: session) end The ``with_transaction`` helper does the following: - It starts a transaction prior to calling the supplied block, and commits the transaction when the block finishes. - If any of the operations in the block, or the commit operation, result in a transient transaction error, the block and/or the commit will be executed again. The block should be idempotent, because it may be called multiple times. The block may explicitly commit or abort the transaction, by calling ``commit_transaction`` or ``abort_transaction``; in this case ``with_transaction`` will not attempt to commit or abort (but may still retry the block on transient transaction errors propagated out of the block). The block will also be retried if the transaction's commit result is unknown. This may happen, for example, if the cluster undergoes an election during the commit. In this case when the block is retried, the primary server of the topology would likely have changed. Currently ``with_transaction`` will stop retrying the block and the commit once 120 seconds pass since the beginning of its execution. This time is not configurable and may change in a future driver version. Note that this does not guarantee the overall runtime of ``with_transactions`` will be 120 seconds or less - just that once 120 seconds of wall clock time have elapsed, further retry attempts will not be initiated. A low level API is also available if more control over transactions is desired. ``with_transaction`` takes the same options as ``start_transaction`` does, which are read concern, write concern and read preference: .. code-block:: ruby session = client.start_session session.with_transaction( read_concern: {level: :majority}, write_concern: {w: 3}, read: {mode: :primary} ) do collection.insert_one({hello: 'world'}, session: session) end Low Level API ============= A transaction can be started by calling the ``start_transaction`` method on a session: .. code-block:: ruby session = client.start_session session.start_transaction It is also possible to specify read concern, write concern and read preference when starting a transaction: .. code-block:: ruby session = client.start_session session.start_transaction( read_concern: {level: :majority}, write_concern: {w: 3}, read: {mode: :primary}) To persist changes made in a transaction to the database, the transaction must be explicitly committed. If a session ends with an open transaction, `the transaction is aborted `_. A transaction may also be aborted explicitly. To commit or abort a transaction, call ``commit_transaction`` or ``abort_transaction`` on the session instance: .. code-block:: ruby session.commit_transaction session.abort_transaction Note: an outstanding transaction can hold locks to various objects in the server, such as the database. For example, the drop call in the following snippet will hang for `transactionLifetimeLimitSeconds `_ seconds (default 60) until the server expires and aborts the transaction: .. code-block:: ruby c1 = Mongo::Client.new(['127.0.0.1:27017']).use(:test_db) session = c1.start_session c1['foo'].insert_one(test: 1) session.start_transaction c1['foo'].insert_one({test: 2}, session: session) c2 = Mongo::Client.new(['127.0.0.1:27017']).use(:test_db) # hangs c2.database.drop Since transactions are associated with server-side sessions, closing the client does not abort a transaction that this client initiated - the application must either call ``abort_transaction`` or wait for the transaction to time out on the server side. In addition to committing or aborting the transaction, an application can also end the session which will abort a transaction on this session if one is in progress: .. code-block:: ruby session.end_session c2 = Mongo::Client.new(['127.0.0.1:27017']).use(:test_db) # ok c2.database.drop Retrying Commits ================ The transaction commit `can be retried `_ if it fails. Here is the Ruby code to do so: .. code-block:: ruby begin session.commit_transaction rescue Mongo::Error => e if e.label?('UnknownTransactionCommitResult') retry else raise end end Transaction Nesting =================== MongoDB does not support nesting transactions. Attempting to call ``start_transaction`` or ``with_transaction`` when a transaction is already in progress will result in an error.