深入理解事務

2023-03-22 15:06:51

介紹事務

事務將應用程式的多個讀、寫操作捆綁在一起成為一個邏輯執行單元。即事務中的所有讀寫是一個執行的整體,整個事務要麼成功(提交)、要麼失敗(中止 或者 回滾)。如果失敗,應用程式可以安全地重試。

這樣,由於不需要擔心部分失敗的情況(無論出於何種原因),應用層的錯誤處理就變得簡單很多。因此事務被創造出來的目的是:簡化應用層的程式設計模型。有了事務,應用程式可以不用考慮某些資料庫內部潛在的錯誤以及複雜的並行性問題,這些都可以交給資料庫來負責處理(我們稱之為安全性保證)

即使沒有事務支援,或許上層應用依然可以工作,然而在沒有原子性保證時,錯誤處理就會異常複雜,而缺乏隔離性則容易出現並行性方面的各種奇怪問題。

事務提供的安全性保證

事務提供的安全性保證即 ACID ,分別代表原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和 永續性(Durability)。

  • ACID 語意中的原子性所定義的特徵是:在出錯時中止事務,並將部分完成的寫入全部丟棄。
  • ACID 語意中的一致性主要是指:資料庫處於應用程式所期待的「預期狀態」。
  • ACID 語意中的隔離性意味著並行執行的多個事務相互隔離,它們不能相互干擾。
  • ACID 語意中的永續性保證一且事務提交成功,即使存在硬體故障或資料庫崩潰,事務所寫入的任何資料也不會消失。

ACID 最早由 TheoHarder 和 Andreas Reuter 於 1983 年為精確描述資料庫的容錯機制而定義。實際上,各家資料庫所實現的 ACID 並不相同。例如,圍繞著 「隔離性」 就存在很多含糊不清的爭議。

原子性

通常,原子是指不可分解為更小粒度的東西。這個術語在計算機的不同領域裡有著相似但卻微妙的差異。例如,多執行緒程式設計中,如果某執行緒執行一個原子操作,這意味著其他執行緒是無法看到該操作的中間結果。它只能處於操作之前或操作之後的狀態,而不能是兩者之間的狀態。

而 ACID 語意中的原子性並不關乎多個操作的並行性,它並沒有描述多個執行緒試圖存取相同的資料會發生什麼情況,後者其實是由 ACID 的隔離性所定義。ACID 原子性其實描述了使用者端發起一個包含多個寫操作的請求時可能發生的情況,例如在完成了一部分寫入之後,系統發生了故障,包括程序崩潰,網路中斷,磁碟變滿或者違反了某種完整性約束等;把多個寫操作納入到一個原子事務,萬一出現了上述故障而導致沒法完成最終提交時,則事務會中止,井且資料庫須丟棄或復原那些區域性完成的更改。

假如沒有原子性保證,當多個更新操作中間發生了錯誤,就需要知道哪些更改已經生效,哪些更改沒有生效,這個尋找過程會非常麻煩。或許應用程式可以重試,但情況類似,並且可能導致重複更新或者不正確的結果。而原子性則大大簡化了這個問題:如果事務已經中止,應用程式可以確定實質上沒有發生任何更改,所以可以安全地重試。

ACID 語意中的原子性所定義的特徵是:在出錯時中止事務,並將部分完成的寫入全部丟棄。換言之,不用擔心資料庫的部分失敗,它總是保證要麼全部成功,要麼全部失敗。

也許可中止性比原子性更為準確,不過我們還是沿用原子性這個慣用術語。

一致性

ACID 語意中的一致性主要是指:資料庫處於應用程式所期待的「預期狀態」。

對資料有特定的預期狀態,任何資料更改必須滿足這些狀態約束(或者恆等條件)。如果某事務從一個有效的狀態開始,並且事務中任何更新操作都沒有違背約束,那麼最後的結果依然符合有效狀態。

這種一致性本質上要求應用層來維護狀態一致(或者恆等),應用程式有責任正確地定義事務來保持一致性。這不是資料庫可以保證的事情:即如果提供的資料修改違背了恆等條件,資料庫很難檢測進而阻止該操作(資料庫可以完成針對某些特定型別的恆等約束檢查,例如使用外來鍵約束或唯一性約束。但通常主要靠應用程式來定義資料的有效/無效狀態,資料庫主要負責儲存)。

原子性,隔離性 和 永續性是資料庫自身的屬性,而 ACID 中的一致性更多是應用層的屬性。應用程式可能借助資料庫提供的原子性和隔離性,以達到一致性,但一致性本身並不源於資料庫。因此,字母 C 其實並不應該屬於 ACID。

隔離性

大多數資料庫都支援多個使用者端同時存取。如果讀取和寫入的是不同資料,這肯定沒有什麼問題;但如果存取相同的記錄,則可能會遇到並行問題(即帶來競爭條件)。

一個簡單的例子如圖所示。假設有兩個使用者端同時增加資料庫中的一個計數器。每個使用者端首先讀取當前值,在使用者端增加 1,然後寫回新值(這裡假設資料庫尚不支援自增操作)。由於有兩次相加,計數器應該由 42 增加到 44,但實際上由於競爭條件最終結果卻是 43。


ACID 語意中的隔離性意味著並行執行的多個事務相互隔離,它們不能相互干擾。例如,如果某個事務進行多次寫入,則另一個事務應該觀察到的是其全部完成(或者一個都沒完成)的結果,而不應該看到中間的部分結果。

經典的資料庫教材把隔離定義為可序列化,這意味著可以假裝一個事務是資料庫上執行的唯一事務。雖然實際上它們可能同時執行,但資料庫系統要確保當事務提交時,其結果與序列執行(一個接一個執行)完全相同。然而實踐中,由於效能問題很少使用序列化隔離,更多的是使用弱隔離級別,在高效能與正確性之間做一個權衡。使用者可以根據自己的業務場景,選擇一個合適的隔離級別。

一些流行的資料庫,如 Oracle 甚至根本就沒有實現序列化隔離。雖然 Oracle 也有聲稱 「序列化」 的功能,但它本質上實現的是快照隔離,快照隔離提供了比序列化更弱的保證。

永續性

資料庫系統本質上是提供一個安全可靠的地方來儲存資料而不用擔心資料丟失。永續性就是這樣的承諾,它保證一且事務提交成功,即使存在硬體故障或資料庫崩潰,事務所寫入的任何資料也不會消失。

對於單節點資料庫 ,永續性通常意味著資料已被寫入非易失性儲存裝置,如硬碟或 SSD。在寫入執行過程中,通常還涉及預寫紀錄檔等,這樣萬一磁碟資料損壞可以進行恢復。而對於支援遠端複製的資料庫,永續性則意味著資料已成功複製到多個節點。為了實現永續性的保證,資料庫必須等到這些寫入或複製完成之後才能報告事務成功提交。

其實不存在完美的永續性。例如,所有的硬碟和所有的備份如果都同時被(人為)銷燬了,那麼資料庫也無能為力。

參考資料

《資料密集型應用系統設計》中文版書