diff --git a/config/htaccess.yaml b/config/htaccess.yaml index 00dc18f39b4..5a851e39a29 100644 --- a/config/htaccess.yaml +++ b/config/htaccess.yaml @@ -4826,6 +4826,13 @@ from: /core/multikey-index-bounds to: /core/indexes/ code: 303 type: redirect +outputs: + - 'before-v2.4' +--- +from: /tutorial/update-if-current +to: /tutorial/isolate-sequence-of-operations +code: 303 +type: redirect outputs: - 'before-v2.4' ... diff --git a/source/includes/toc-crud-tutorials.yaml b/source/includes/toc-crud-tutorials.yaml index 8942d9ea0c2..8666298ae10 100644 --- a/source/includes/toc-crud-tutorials.yaml +++ b/source/includes/toc-crud-tutorials.yaml @@ -41,10 +41,14 @@ description: | --- file: /tutorial/isolate-sequence-of-operations description: | - Use the :operator:` isolated` operator to *isolate* a + Use the :update:`$isolated` operator to *isolate* a single write operation that affects multiple documents, preventing other operations from interrupting the sequence of write operations. --- +file: /tutorial/update-if-current +description: | + Update a document only if it has not changed since it was last read. +--- file: /tutorial/create-an-auto-incrementing-field description: | Describes how to create an incrementing sequence number for the diff --git a/source/tutorial/isolate-sequence-of-operations.txt b/source/tutorial/isolate-sequence-of-operations.txt index 549ff2542a0..6b2470eb9ad 100644 --- a/source/tutorial/isolate-sequence-of-operations.txt +++ b/source/tutorial/isolate-sequence-of-operations.txt @@ -13,109 +13,48 @@ more than one collection. When a single write operation modifies multiple documents, the operation as a whole is not atomic, and other operations may -interleave. The modification of a single document, or record, is always -atomic, even if the write operation modifies multiple sub-documents -*within* the single record. +interleave. The modification of a single document is always atomic, even if +the write operation modifies multiple sub-documents *within* the single record. -No other operations are atomic; however, you can *isolate* a +No other operations are atomic. However, you can *isolate* a single write operation that affects multiple documents using the -:doc:`isolation operator `. +:doc:`isolation operator `. Using this +operator, you can update a set of documents without allowing other processes +to update in between your writes. -This document describes one method of updating documents *only* if the -local copy of the document reflects the current state of the document -in the database. In addition the following methods provide a way to -manage isolated sequences of operations: +It is important to understand that this is *not* true atomicity, because a +failed write will not roll back the preceding writes. Additionally, this +operator does *not* work on sharded clusters. -- the :method:`~db.collection.findAndModify()` - provides an isolated update and return operation. +Approaches +---------- -- :doc:`/tutorial/perform-two-phase-commits` +``findAndModify()`` +~~~~~~~~~~~~~~~~~~~ -- Create a :ref:`unique index `, to ensure that a - key doesn't exist when you insert it. +The :method:`~db.collection.findAndModify()` method provides an isolated +update-and-return operation. This allows you to update a document and get back +the updated state, without having to worry about another process having mutated +it. -.. _tutorial-atomic-update-if-current: +Two Phase Commit +~~~~~~~~~~~~~~~~ -Update if Current ------------------ - -In this pattern, you will: - -- query for a document, - -- modify the fields in that document - -- and update the fields of a document *only if* the fields have not - changed in the collection since the query. - -Consider the following example in JavaScript which attempts to update -the ``qty`` field of a document in the ``products`` collection: - -.. versionchanged:: 2.6 - The :method:`db.collection.update()` method now returns a - :method:`WriteResult()` object that contains the status of - the operation. Previous versions required an extra - :method:`db.getLastErrorObj()` method call. - -.. code-block:: javascript - - var myCollection = db.products; - var myDocument = myCollection.findOne( { sku: 'abc123' } ); +:doc:`Two Phase Commit ` is a general +protocol for multi-document transactions that you may implement in your +application logic. - if (myDocument) { +Unique Indexes +~~~~~~~~~~~~~~ - var oldQty = myDocument.qty; +Create a :ref:`unique index ` to ensure that each document +has a distinct value for a specific field. In situations where you assign a +value that does not already exist in a collection, this prevents a second +process from assigning the same value elsewhere at the same time. - if (myDocument.qty < 10) { - myDocument.qty *= 4; - } else if ( myDocument.qty < 20 ) { - myDocument.qty *= 3; - } else { - myDocument.qty *= 2; - } - - var results = myCollection.update( - { - _id: myDocument._id, - qty: oldQty - }, - { - $set: { qty: myDocument.qty } - } - ); - - if ( results.hasWriteError() ) { - print("unexpected error updating document: " + tojson( results )); - } else if ( results.nMatched == 0 ) { - print("No update: no matching document for { _id: " + myDocument._id + ", qty: " + oldQty + " }") - } - - } - -Your application may require some modifications of this pattern, such -as: - -- Use the entire document as the query in the - :method:`~db.collection.update()` operation, to generalize the - operation and guarantee that the original document was not modified, - rather than ensuring that as single field was not changed. - -- Add a version variable to the document that applications increment - upon each update operation to the documents. Use this version - variable in the query expression. You must be able to ensure that - *all* clients that connect to your database obey this constraint. - -- Use :update:`$set` in the update expression to modify only your - fields and prevent overriding other fields. - -- Use one of the methods described in :doc:`/tutorial/create-an-auto-incrementing-field`. +Update if Current +~~~~~~~~~~~~~~~~~ -.. Maybe incorporate the blurb: "MongoDB does not - support traditional locking and complex transactions for a number of - reasons: First, in sharded environments, distributed locks could be - expensive and slow. MongoDB's goal is to be lightweight and fast. We - dislike the concept of deadlocks. We want the system to be simple and - predictable without these sort of surprises. We want MongoDB to work - well for realtime problems. If an operation may execute which locks - large amounts of data, it might stop some small light queries for an - extended period of time." +:doc:`Update if Current ` is a pattern for +changing a document without potentially overwriting the changes of other +processes. diff --git a/source/tutorial/update-if-current.txt b/source/tutorial/update-if-current.txt new file mode 100644 index 00000000000..ac0f804c611 --- /dev/null +++ b/source/tutorial/update-if-current.txt @@ -0,0 +1,106 @@ +========================== +Update Document if Current +========================== + +.. default-domain:: mongodb + +Overview +------------ + +To isolate a group of document modifications on a set of documents, always +attempt to use a combination of unique indexes and a single +:method:`db.collection.update()` operation. Because all modifications are atomic +within a single document, a well-designed data model allows applications to +group related operations together. + +Nevertheless there are cases where a logical data modification operation requires +application-based logic. Because MongoDB cannot isolate application-based +update logic, your code must ensure that its multi-stage update operations +are logically valid and not affected by other concurrent operations in your +system. + +.. _tutorial-atomic-update-if-current: + +Update if Current Pattern +------------------------- + +Overview +~~~~~~~~ + +In the :ref:`Update if Current +` pattern, you will: + +- query for a document, + +- modify the fields in that document, + +- and update the fields of a document *only if* the fields have not + changed in the collection since the query. + +Example +~~~~~~~ + +Consider the following example in the MongoDB shell which attempts to update +the ``qty`` field of a document in the ``products`` collection: + +.. versionchanged:: 2.6 + The :method:`db.collection.update()` method now returns a + :method:`WriteResult()` object that contains the status of + the operation. Previous versions required an extra + :method:`db.getLastErrorObj()` method call. + +.. code-block:: javascript + + // Update a document only if it is safe to do so + function updateIfCurrent( collection, query, modifyFunc ) { + var myDocument = collection.findOne( query ); + + if ( myDocument ) { + var newField = modifyFunc( myDocument ); + var updateDoc = { $set: newField } + + // This will fail to match any documents if the underlying document + // has some field different from myDocument. + var results = collection.update( myDocument, updateDoc ); + + if ( results.hasWriteError() ) { + print( "unexpected error updating document: " + tojson( results ) ); + } else if ( results.nMatched === 0 ) { + print( "No update: no matching document for " + tojson( query ) ); + } + } + } + + // Apply an update function to a db.products document, or fail. + updateIfCurrent( db.products, { sky: "abc123" }, function( doc ) { + var qty = doc.qty + + if ( qty < 10 ) { + qty *= 4; + } else if ( qty < 20 ) { + qty *= 3; + } else { + qty *= 2; + } + + return { qty: qty }; + } ); + +Additional Information +---------------------- + +A different approach is to add a ``version`` field to the document that +applications increment upon each update operation to the documents. Use this +version variable in the query expression. You must be able to ensure that +*all* clients that connect to your database obey this constraint. + +If you need to associate increasing numbers with documents in a collection, +you can use one of the methods described in +:doc:`/tutorial/create-an-auto-incrementing-field`. + +Use :update:`$set` in the update expression to modify only your fields and +prevent overriding other fields. + +:doc:`Two Phase Commit ` is a general +protocol for multi-document transactions that you may implement in your +application logic.