如果兩個 transaction 沒有接觸到相同的資料,則它們可以很愉快的 並發 (concurrent) 執行,因為他們彼此不依賴。
並發的問題只會發生在當某個 transaction 要讀取的資料正在被另一個並發執行的 transaction 修改,或者兩個 transaction 同時修改一樣的資料。
並發的 bug 很難測試或重現,所以資料庫才會透過 ACID,向我們保證不會發生並發的 bug,就理論上來說,隔離性應該能保護工程師遠離並發的惡夢,所以最好就是不要用並發啦,也就是實施 序列化隔離 (serializable isolation), 一次只有一個 transaction 在執行完全符合隔離性保證。
一次只能讓一個 transaction 執行,這就代表序列化隔離會有很嚴重的效能問題,因此在現實世界中,許多支援 ACID 的資料庫,大多數的預設都是使用 弱等級的隔離 (Weak Isolation Levels),因為隔離性等級較弱,所以會比較難理解,也代表還是有可能會出現並發資料問題或並發 bug,所以就讓我們來好好了解各個弱等級隔離的差異,然後搭配應用程式開發達成盡量減少並發 bug 吧!
等級 1 的隔離是 read committed ,它保證了以下兩點:
讓我們研究研究這 2 點。
dirty read 就是在你的 transaction 裡讀到另一個 transaction 還沒 commit 的資料。
無 dirty read 的結果如下圖,User 2 只會讀取到已被 commit 的資料(等於是 User 1 的多次寫入在 commit 後會一起成為可視)。
dirty write 就是一個 transaction 在還沒 commit 的情況下,其值被另一個 transaction 給覆寫了。
read committed 等級的隔離該如何避免 dirty write 呢?一般的做法就是延遲第二個 transaction 的寫入,直到第一個的 transaction 寫入 commit 或中斷 (aborted)。
如果 transaction 是需要更新多個物件,如下圖,dirty write 就會導致汽車是 Bob 買到,但發票是寄給 Alice 了。
然而,read committed 無法避免如下圖針對 計數器 (counter) 資源的 競爭條件 (race condition) 寫入 , User 2 的寫入是發生在 User 1 已經 commit 之後的動作,所以符合 read committed 的保證,無 dirty write。但它的值依舊不正確,我們會在 Day 5 討論如何安全的在 counter 累加 1。
read committed 是最流行、常用的隔離等級,也是許多資料庫的預設隔離等級。
避免 dirty write 的實現可以使用 row-level 的鎖,當 transaction 想要修改特定物件 (row 或 document) 時,它必須取得該物件的鎖,持有該鎖直到該 transaction commit 或中斷 (aborted),一次只有一個 transaction 能取得物件鎖。
而避免 dirty read 的實現呢?一個選項是採用避免 dirty write 的方法,有物件鎖才能讀取資料;但倘若此時有個跑很久的寫入 transaction 正在寫入資料呢?所有的讀取就得排隊等 transaction 結束才能作業了,如此就會嚴重傷害那些 read-only transaction 的回覆時間了,顯然這不是個好方法。
基於以上理由,大多數的資料庫實現 避免 dirty read 的方法就是像上圖 7-4 那樣,在 User 1 執行資料寫入時,除了要取得物件鎖之外,資料庫也會先記住舊的資料讓其他的 transaction 讀取,commit 後就替換成新的資料。
明天講另一個 Weak Isolation Levels - 快照隔離 (Snapshot Isolation)。