《設計資料密集型應用/DDIA》精要翻譯(五) :事務
1. 事務的ACID
雖然ACID我們已經說濫了,這裡我想再說一下一致性和隔離性。
一致性
一致性在不同的術語中有不同的含義:
- 在前面那篇部落格中,我們討論了副本之間的一致性(比如最終一致性、讀已之寫一致性等)
- 在CAP中,一致性表示可線性化(即只要有一個客戶端成功寫入,別的客戶端後續的讀取必須能看到剛剛寫入的值。 詳見本系列第七篇。)
- 在ACID中,指資料庫在事務前後保持正確(比如轉賬前後兩人總餘額不變)
隔離性
在我N年前的一篇部落格中,我列了資料庫不同事務隔離級別以及在多個事務併發時可能遇到的問題:
髒讀:事務A修改了一個數據,但未提交,事務B讀到了事務A未提交的更新結果,如果事務A提交失敗,事務B讀到的就是髒資料。
不可重複讀:在同一個事務中,對於同一份資料讀取到的結果不一致。比如,事務B在事務A提交前讀到的結果,和提交後讀到的結果可能不同。不可重複讀出現的原因就是事務併發修改記錄,要避免這種情況,最簡單的方法就是對要修改的記錄加鎖,這回導致鎖競爭加劇,影響效能。另一種方法是通過MVCC可以在無鎖的情況下,避免不可重複讀。
幻讀:在同一個事務中,同一個查詢多次返回的結果不一致。事務A新增了一條記錄,事務B在事務A提交前後各執行了一次查詢操作,發現後一次比前一次多了一條記錄。幻讀可以通過加間隙鎖的方式來解決。
2. 事務隔離級別與其實現
隔離級別
Read Uncommitted:最低的隔離級別,什麼都不需要做,一個事務可以讀到另一個事務未提交的結果。所有的併發事務問題都會發生。
Read Committed:只有在事務提交後,其更新結果才會被其他事務看見。可以解決髒讀問題。
Repeated Read:在一個事務中,對於同一份資料的讀取結果總是相同的,無論是否有其他事務對這份資料進行操作,以及這個事務是否提交。可以解決髒讀、不可重複讀。
Serialization:事務序列化執行,隔離級別最高,犧牲了系統的併發性。可以解決併發事務的所有問題。
實現
讀已提交的實現
- 對於寫操作: 加上行鎖 (防止髒寫)
- 對於讀操作:資料庫記住舊值,和持有寫入鎖的事務寫入的新值。在另一個事務提交前,別的事務讀到舊值;在另一個事務提交後,別的事務讀到新值
可重複讀的實現
通常使用MVCC(多版本併發控制)來實現, 比如mysql innodb引擎中的實現方式:
參考這篇文章
事務操作:
- 每個事務在開始的時候都有一個唯一的且遞增的id, 這個事務對資料庫的任何寫入,被寫入的資料都會被標記上這個事務id
- 資料庫表中的每行都有一個created_by欄位以及一個deleted_by欄位,created_by的值是插入這行的事務id,deleted_by的值初始為空,如果有事務執行delete操作,就賦值為該事務id。資料庫的垃圾回收機制會在沒有事務訪問被刪除事務的時候執行回收操作
- update操作在資料庫中被解析成create和delete操作,比如事務tx1 update id = 1的行,那麼資料庫中實際上有兩行:一行是delete by tx1, 一行是create by tx1
事務與快照的可見性:
- 事務開始時,資料庫會列出當時所有其他未提交或還在執行的事務,在整個事務過程中,忽略這些事務的寫入(儘管他們可能在後面被提交)
- 忽略所有被中止的事務的寫入。
- 忽略任何事務id大於自己的事務的寫入
具體以InnoDB儲存引擎來說:在事務隔離級別READ COMMITTED和REPEATABLE READ(InnoDB儲存引擎的預設事務隔離級別)下,InnoDB儲存引擎使用非鎖定的一致性讀。然而,對於快照資料的定義卻不相同。在READ COMMITTED事務隔離級別下,對於快照資料,非一致性讀總是讀取被鎖定行的最新一份快照資料。而在REPEATABLE READ事務隔離級別下,對於快照資料,非一致性讀總是讀取事務開始時的行資料版本。
在預設的事務隔離級別下,即REPEATABLE READ下,InnoDB儲存引擎採用Next-Key Locking機制來避免Phantom Problem(幻像問題)。這點可能不同於與其他的資料庫,如Oracle資料庫,因為其可能需要在SERIALIZABLE的事務隔離級別下才能解決Phantom Problem。
序列化的實現
要實現序列化,有以下幾種方式:
- 真的序列化,一個接一個(ps: 應該不會有系統淪落到要用這種方式的)
- 兩階段鎖的方式(2PL, 不同於2PC!在資料庫中被廣泛使用。)
- 可序列化的快照隔離(serializable snapshot isolation, SSI), 一種樂觀併發控制技術
2PL
兩階段鎖協議是指所有事務必須分兩個階段對資料項加鎖和解鎖:
- 在對任何資料進行讀、寫操作之前,首先要申請並獲得對該資料的封鎖
所謂”兩段”鎖的含義是,事務分為兩個階段,第一階段是獲得封鎖,也稱為擴充套件階段。在這階段,事務可以申請獲得任何資料項上的任何型別的鎖但是不能釋放任何鎖。第二階段是釋放封鎖,也稱為收縮階段。在這階段,事務可以釋放任何資料項上的任何型別的鎖,但是不能再申請任何鎖。
參考: 兩階段鎖
SSI
SSI於2008年才被提出,它實現了完整的可序列化隔離級別,與MVCC相比只有很小的效能損失。雖然現在還處於實踐中證明自己的階段,但是前途無量。
2PL是一種悲觀併發控制,而SSI是一種樂觀併發控制。
SSI基於快照隔離(即上文MVVC),在它的基礎上,SSI加入了一種檢查寫入衝突的演算法,來決定終止哪些事務。
舉個例子,如下圖: 事務42先提交,並且成功了。當事務43提交時,發現事務42已經提交了與自己相沖突的寫入,所以必須中止事務43。