當兩個事務更改相同的行/值時,正確的數據庫行為是什麼?
我正在為嵌入式 NoSQL 數據庫編寫 C++ 中的數據庫 LRU 記憶體以解決性能問題,並且我試圖理解其背後的正確假設行為和理念。
假設有一個處於某個狀態 X 的 NoSQL 數據庫。我們從相同的隔離狀態 X 啟動事務 1 (tx1) 並啟動事務 2 (tx2)。兩個事務都嘗試更改相同的鍵/值對。每筆交易都將值更改為某個值,兩個值不相等。Tx1 送出,然後 tx2 送出。數據庫的正確行為是什麼?
- 新值是從 tx2 送出的值,因為它覆蓋了 tx1
- 新值是從 tx1 送出的值,因為送出 tx2 應該會失敗
還是答案是別的?
如果它符合 ACID,有人可以詳細說明應該如何程式這樣的系統嗎?
我要記憶體的數據庫是LMDB,它聲稱符合 ACID 標準。
背景
這個問題詢問了 ACID 中的 I,它代表隔離:
- 原子:事務的所有組件作為一個單元成功或失敗。
- 一致:事務使數據庫處於不違反任何活動約束的狀態。
- 隔離:事務與其他並發事務的影響隔離到目前隔離級別指定的程度。
- 持久:對使用者數據的已送出更改是持久的(可恢復的)。
最隔離的標準隔離級別稱為serializable。
SQL-92 標準中對可序列化隔離級別的定義包含以下文本:
可串列執行被定義為並行執行 SQL 事務的操作的執行,其產生與那些相同 SQL 事務的某些串列執行相同的效果。串列執行是每個 SQL 事務在下一個 SQL 事務開始之前執行完成。
在真正的序列化執行(每個事務實際上在下一個事務開始之前完全執行到完成)和可序列化隔離之間有一個重要的區別,其中事務只需要具有與串列執行相同的效果**(**在一些未指定的順序)。
只要這些事務的效果仍然對應於某些可能的串列執行順序,一個真實的數據庫系統就可以在物理上及時地重疊可串列事務的執行(增加並發性)。
問題
我們從相同的隔離狀態 X 開始事務 1 (tx1) 和事務 2 (tx2)。兩個事務都嘗試更改相同的鍵/值對。每筆交易都將值更改為某個值,兩個值不相等。Tx1 送出,然後 tx2 送出。數據庫的正確行為是什麼?
兩種串列執行中的任何一種都是可能的:
- T1 寫入值 V1。T2 寫入值 V2。
- T2 寫入值 V2。T1 寫入值 V1。
兩個事務送出後的觀察值可能是 V1或V2。
根據不同的串列時間表(T1 然後 T2 或 T2 然後 T1),兩者都是正確的。
作為第二個範例,考慮兩個事務寫入取決於原始值的值。例如,原始值為 100,交易 T1 增加 10%,交易 T2 增加 50%:
可序列化時間表 A:
- T1 讀取 100,寫入 110。 (+10%)
- T2 讀取 110,寫入 165。(+50%)
可序列化時間表 B:
- T2 讀取 100,寫入 150。 (+50%)
- T1 讀取 150,寫入 165。(+10%)
現在,更複雜的操作可能會產生與不可能的串列事務調度相對應的效果。在這種情況下,一個事務將因錯誤而失敗並回滾。
(當引擎無法確定串列調度是可能的時,一些數據庫引擎也可能引發錯誤併中止事務,即使它在邏輯上是可能的。)
根據LMDB的文件:
寫入是完全序列化的;一次只能有一個寫事務處於活動狀態,這保證了寫者永遠不會死鎖。
這證實了 LMDB 實現了可序列化的隔離級別。
另一個問題
您可能打算詢問有關Lost Update的問題,其中 T1 所做的更改(不希望地)被 T2 覆蓋,因此“失去”。如果是這樣,請查看 Q & A UPDATE 語句行為。