[SERVER-74261] transaction behavior Created: 22/Feb/23  Updated: 27/Oct/23  Resolved: 01/Aug/23

Status: Closed
Project: Core Server
Component/s: None
Affects Version/s: 4.4.4
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: LiuChang N/A Assignee: Pavithra Vetriselvan
Resolution: Works as Designed Votes: 0
Labels: inconsistent, transactions
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Assigned Teams:
Storage Execution
Operating System: ALL
Steps To Reproduce:

case 1:

T1 execute stmt: 't1.put(0, 0)'
T1 execute stmt: 't1.put(1, 0)'
T1 start transaction success
T1 execute stmt: 't1.put(0, 1)'
                                        T2 start transaction success
                                        T2 execute stmt: 't1.put(1, 1)'
                                        T2 execute stmt: 't1.get(0)'
                                           current_result: 
                                             (0,0
T1 execute stmt: 't1.get(1)'
   current_result: 
     (1,0
                                        T2 COMMIT transaction success
T1 COMMIT transaction success
                                                                                T3 execute stmt: 't1.get(*)'
                                                                                   current_result: 
                                                                                     (0,1) (1,1

case 2:

T1 execute stmt: 't1.put(0, 0)'
T1 execute stmt: 't1.put(1, 0)'
T1 start transaction success
T1 execute stmt: 't1.put(0, 1)'
                                        T2 start transaction success
                                        T2 execute stmt: 't1.put(1, 1)'
                                        T2 execute stmt: 't1.get(0)'
                                           current_result: 
                                             (0,0)                                         T2 COMMIT transaction success
T1 execute stmt: 't1.get(1)'
   current_result: 
     (1,0) T1 COMMIT transaction success
                                                                                T3 execute stmt: 't1.get(*)'
                                                                                   current_result: 
                                                                                     (0,1) (1,1

case 3:

T1 execute stmt: 't1.put(0, 0)'
T1 execute stmt: 't1.put(1, 0)'
T1 execute stmt: 't1.put(2, 0)'
T1 start transaction success
T1 execute stmt: 't1.put(0, 1)'
                                        T2 start transaction success
                                        T2 execute stmt: 't1.put(1, 1)'
                                        T2 execute stmt: 't1.get(0)'
                                           current_result: 
                                             (0,0
                                                                                T3 start transaction success
                                                                                T3 execute stmt: 't1.put(2, 1)'
                                                                                T3 execute stmt: 't1.get(1)'
                                                                                   current_result: 
                                                                                     (1,0
T1 execute stmt: 't1.get(2)'
   current_result: 
     (2,0
T1 COMMIT transaction success
                                        T2 COMMIT transaction success
                                                                                T3 COMMIT transaction success
                                                                                                                        T4 execute stmt: 't1.get(*)'
                                                                                                                           current_result: 
                                                                                                                             (0,1) (1,1) (2,1

case 4:

T1 execute stmt: 't1.put(0, 0)'
T1 execute stmt: 't1.put(1, 0)'
T1 start transaction success
T1 execute stmt: 't1.get(0)'
   current_result: 
     (0,0)                                                                    T2 start transaction success
                                        T2 execute stmt: 't1.get(1)'
                                           current_result: 
                                             (1,0)                                                                                T2 execute stmt: 't1.put(0, 1)'
T1 execute stmt: 't1.put(1, 1)'
T1 COMMIT transaction success
                                        T2 COMMIT transaction success
                                                                                T3 execute stmt: 't1.get(*)'
                                                                                   current_result: 
                                                                                     (0,1) (1,1

case 5:

T1 execute stmt: 't1.put(0, 0)'
T1 execute stmt: 't1.put(1, 0)'
T1 start transaction success
T1 execute stmt: 't1.get(0)'
   current_result: 
     (0,0)                                         T2 start transaction success
                                        T2 execute stmt: 't1.get(1)'
                                           current_result: 
                                             (1,0)                                         T2 execute stmt: 't1.put(0, 1)'
T1 COMMIT transaction success
T1 execute stmt: 't1.put(1, 1)'
                                        T2 COMMIT transaction success
                                                                                T3 execute stmt: 't1.get(*)'
                                                                                   current_result: 
                                                                                     (0,1) (1,1

case 6:

T1 execute stmt: 't1.put(0, 0)'
T1 execute stmt: 't1.put(1, 0)'
T1 execute stmt: 't1.put(2, 0)'
T1 start transaction success
T1 execute stmt: 't1.get(2)'
   current_result: 
     (2,0)                                         T2 start transaction success
                                        T2 execute stmt: 't1.get(0)'
                                           current_result: 
                                             (0,0)                                                                                 T3 start transaction success
                                                                                T3 execute stmt: 't1.get(1)'
                                                                                   current_result: 
                                                                                     (1,0)T1 execute stmt: 't1.put(0, 1)'
                                        T2 execute stmt: 't1.put(1, 1)'
                                                                                T3 execute stmt: 't1.put(2, 1)'
T1 COMMIT transaction success
                                        T2 COMMIT transaction success
                                                                                T3 COMMIT transaction success
                                                                                                                        T4 execute stmt: 't1.get(*)'
                                                                                                                           current_result: 
                                                                                                                             (0,1) (1,1) (2,1) 

 

Sprint: Execution NAMR Team 2023-07-24, Execution NAMR Team 2023-08-07
Participants:

 Description   

I execute some histories for multiple transactions in different sessions. There are some read-write dependencies between these transactions and I expect that at least one transaction should be aborted.

However, all transactions commit successfully unexpectedly which means there are data anomailies in these histories.



 Comments   
Comment by Pavithra Vetriselvan [ 19/Jul/23 ]

Hi williamcliu@tencent.com, thanks for filing this ticket and attaching detailed examples. This behavior is expected in MongoDB.

Transactions in MongoDB are implemented using snapshot isolation, which is explained in depth here. This allows for “read your own writes” behavior, which means that reads within the same transaction can read its modifications to a document. However, reads outside of a transaction cannot read those writes until the transaction commits, as illustrated by your examples.

The transactions in your examples do not abort because snapshot isolation doesn’t require that transactions be aborted for read-write conflicts. Since transactions using snapshot isolation operate on a snapshot of the data, they can commit as long as values updated by the transaction were not updated externally since the snapshot was taken. Doing so would result in a write-write conflict and the transaction would abort.

We can use that property to enforce behavior that is usually achieved via serializability. With serializability, a transaction T1 would have to occur “first” and be visible to another transaction T2. In contrast, one of the main benefits of snapshot isolation is the performance we get from running more transactions concurrently. In your examples, you could introduce a field per document that every transaction modifies (increment by 1). This will force an order since concurrent transactions will now get write-write conflicts. In this case, if T1 commits before T2, T2 will abort and have to be retried entirely by the client until it succeeds.

Generated at Thu Feb 08 06:26:57 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.