Skip to content

Commit 80311f8

Browse files
authored
DOCSP-36046 Transaction FindOneAndUpdate Doesn't Lock Document (#6100) (#6735)
* DOCSP-36046 Transaction FindOneAndUpdate Doesn't Lock Document (#6100) * DOCSP-36046 Transaction FindOneAndUpdate Doesn't Lock Document * wording * IF feedback: add stale reads to glossary, add procedure steps * formatting * typo * * * IF suggestions * io code block * typo * hide default visibility * nit edit * AK feedback * wording * * * * * * * Asya feedback * Asya feedback 2/2 * final feedback * build error
1 parent df2ec94 commit 80311f8

File tree

3 files changed

+85
-30
lines changed

3 files changed

+85
-30
lines changed

source/core/read-preference-use-cases.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ read preference modes:
5353

5454
Use :readmode:`primaryPreferred` if you want an application to
5555
read from the primary under normal circumstances, but to
56-
allow stale reads from secondaries when the primary is unavailable. This provides a
57-
"read-only mode" for your application during a failover.
56+
allow :term:`stale reads <stale read>` from secondaries when the primary is
57+
unavailable.
5858

5959
.. _read-preference-counter-indications:
6060

source/includes/extracts-transactions.yaml

Lines changed: 78 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -203,37 +203,87 @@ content: |
203203
ref: transactions-stale-reads
204204
content: |
205205
206-
Read operations inside a transaction can return stale data. That is,
207-
read operations inside a transaction are not guaranteed to see
208-
writes performed by other committed transactions or
209-
non-transactional writes. For
210-
example, consider the following sequence: 1) a transaction is
211-
in-progress 2) a write outside the transaction deletes a document 3)
212-
a read operation inside the transaction is able to read the
213-
now-deleted document since the operation is using a snapshot from
214-
before the write.
206+
Read operations inside a transaction can return old data, which is known as a
207+
:term:`stale read`. Read operations inside a transaction are not guaranteed
208+
to see writes performed by other committed transactions or
209+
non-transactional writes. For example, consider the following sequence:
210+
211+
#. A transaction is in-progress.
212+
213+
#. A write outside the transaction deletes a document.
214+
215+
#. A read operation inside the transaction can read the now-deleted document
216+
since the operation uses a snapshot from before the write operation.
215217
216218
To avoid stale reads inside transactions for a single document, you
217-
can use the :method:`db.collection.findOneAndUpdate()` method. For
218-
example:
219-
220-
.. code-block:: javascript
221-
222-
session.startTransaction( { readConcern: { level: "snapshot" }, writeConcern: { w: "majority" } } );
223-
224-
employeesCollection = session.getDatabase("hr").employees;
225-
226-
employeeDoc = employeesCollection.findOneAndUpdate(
227-
{ _id: 1, employee: 1, status: "Active" },
228-
{ $set: { employee: 1 } },
229-
{ returnNewDocument: true }
230-
);
231-
232-
- If the employee document has changed outside the transaction, then
233-
the transaction aborts.
219+
can use the :method:`db.collection.findOneAndUpdate()` method. The following
220+
:binary:`~bin.mongo` example demonstrates how you can use
221+
``db.collection.findOneAndUpdate()`` to take a :term:`write lock` and ensure
222+
that your reads are up to date:
223+
224+
.. procedure::
225+
:style: normal
226+
227+
.. step:: Insert a document into the ``employees`` collection
228+
229+
.. code-block:: javascript
230+
:copyable: true
231+
232+
db.getSiblingDB("hr").employees.insertOne(
233+
{ _id: 1, status: "Active" }
234+
)
235+
236+
.. step:: Start a session
237+
238+
.. code-block:: javascript
239+
:copyable: true
240+
241+
session = db.getMongo().startSession( { readPreference: { mode: "primary" } } )
242+
243+
.. step:: Start a transaction
244+
245+
.. code-block:: javascript
246+
:copyable: true
247+
248+
session.startTransaction( { readConcern: { level: "snapshot" }, writeConcern: { w: "majority" } } )
249+
250+
employeesCollection = session.getDatabase("hr").employees
251+
252+
.. step:: Use ``db.collection.findOneAndUpdate()`` inside the transaction
253+
254+
.. code-block:: javascript
255+
:copyable: true
256+
257+
employeeDoc = employeesCollection.findOneAndUpdate(
258+
{ _id: 1, status: "Active" },
259+
{ $set: { lockId: ObjectId() } },
260+
{ returnNewDocument: true }
261+
)
262+
263+
Note that inside the transaction, the ``findOneAndUpdate`` operation
264+
sets a new ``lockId`` field. You can set ``lockId`` field to any
265+
value, as long as it modifies the document. By updating the
266+
document, the transaction acquires a lock.
267+
268+
If an operation outside of the transaction attempts to modify the
269+
document before you commit the transaction, MongoDB returns a write
270+
conflict error to the external operation.
271+
272+
.. step:: Commit the transaction
273+
274+
.. code-block:: javascript
275+
:copyable: true
276+
277+
session.commitTransaction()
278+
279+
After you commit the transaction, MongoDB releases the lock.
280+
281+
.. note::
282+
283+
If any operation in the transaction fails, the transaction
284+
aborts and all data changes made in the transaction are discarded
285+
without ever becoming visible in the collection.
234286
235-
- If the employee document has not changed, the transaction returns
236-
the document and locks the document.
237287
---
238288
ref: transactions-read-concern-majority
239289
content: |

source/reference/glossary.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,11 @@ Glossary
10381038
state electronics for persistence instead of rotating platters
10391039
and movable read/write heads used by mechanical hard drives.
10401040

1041+
stale read
1042+
A stale read refers to when a transaction reads old (stale) data that has
1043+
been modified by another transaction but not yet committed to the
1044+
database.
1045+
10411046
standalone
10421047
An instance of :binary:`~bin.mongod` that runs as a single server
10431048
and not as part of a :term:`replica set`. To convert it to a

0 commit comments

Comments
 (0)