diff --git a/core/mvcc/database/mod.rs b/core/mvcc/database/mod.rs index 6c6e49053..da04221a2 100644 --- a/core/mvcc/database/mod.rs +++ b/core/mvcc/database/mod.rs @@ -58,7 +58,9 @@ impl LogRecord { /// versions switch to tracking timestamps. #[derive(Clone, Debug, PartialEq, PartialOrd)] enum TxTimestampOrID { + /// A committed transaction's timestamp. Timestamp(u64), + /// The ID of a non-committed transaction. TxID(TxID), } @@ -365,6 +367,10 @@ impl MvStore MvStore MvStore = tx.write_set.iter().map(|v| *v.value()).collect(); drop(tx); // Postprocessing: inserting row versions and logging the transaction to persistent storage. @@ -568,7 +572,9 @@ impl MvStore MvStore MvStore( txs: &SkipMap>, tx: &Transaction, @@ -731,12 +749,16 @@ pub(crate) fn is_write_write_conflict( Some(TxTimestampOrID::TxID(rv_end)) => { let te = txs.get(&rv_end).unwrap(); let te = te.value().read().unwrap(); - match te.state.load() { - TransactionState::Active | TransactionState::Preparing => tx.tx_id != te.tx_id, - _ => false, + if te.tx_id == tx.tx_id { + return false; } + te.state.load() != TransactionState::Aborted } - Some(TxTimestampOrID::Timestamp(_)) => false, + // A non-"infinity" end timestamp (here modeled by Some(ts)) functions as a write lock + // on the row, so it can never be updated by another transaction. + // Ref: https://www.cs.cmu.edu/~15721-f24/papers/Hekaton.pdf , page 301, + // 2.6. Updating a Version. + Some(TxTimestampOrID::Timestamp(_)) => true, None => false, } } diff --git a/core/mvcc/database/tests.rs b/core/mvcc/database/tests.rs index 8cb1c3027..b317a15d2 100644 --- a/core/mvcc/database/tests.rs +++ b/core/mvcc/database/tests.rs @@ -382,7 +382,7 @@ fn test_fuzzy_read() { table_id: 1, row_id: 1, }, - data: "Hello".to_string(), + data: "First".to_string(), }; db.insert(tx1, tx1_row.clone()).unwrap(); let row = db @@ -419,7 +419,7 @@ fn test_fuzzy_read() { table_id: 1, row_id: 1, }, - data: "World".to_string(), + data: "Second".to_string(), }; db.update(tx3, tx3_row).unwrap(); db.commit_tx(tx3).unwrap(); @@ -436,6 +436,18 @@ fn test_fuzzy_read() { .unwrap() .unwrap(); assert_eq!(tx1_row, row); + + // T2 tries to update the row, but fails because T3 has already committed an update to the row, + // so T2 trying to write would violate snapshot isolation if it succeeded. + let tx2_newrow = Row { + id: RowID { + table_id: 1, + row_id: 1, + }, + data: "Third".to_string(), + }; + let update_result = db.update(tx2, tx2_newrow); + assert_eq!(Err(DatabaseError::WriteWriteConflict), update_result); } #[test]