Transaction 併發錯誤與隔離層級

--

上一篇文章介紹了資料庫 Transaction 與 ACID 的特性,可以避免一些資料不一致的問題,然而就算有 Atomicity 的特性,在併發的 transaction 狀況下,還是有可能發生不預期的錯誤,今天透過這篇文章就要來簡單介紹單機狀況下併發 transaction 有可能會發生的錯誤,與對應的解決辦法。

(以下圖片皆出自讀書會成員 FuFu 的簡報)

髒讀 Dirty Read

一個事務在處理過程中讀取了另外一個事務未提交的數據。

上面的例子中 Transaction B 雖然將 money 改為 1000 了,但卻還沒有將結果 commit,如果在這短暫的時間中 Transaction A 也對 money 進行讀取,會得到的值是 1000,但如果 Transaction B 後來做了 rollback,money 的值會變回原本的值,然而 Transaction A 還會誤以為值就是 1000,讀取到錯誤的結果,你說髒不髒呢?

不可重複讀 Non-repeatable read

一個 Transaction範圍內,多次查詢某個數據,卻得到不同的結果。

它與髒讀的區別是髒讀是讀到未提交的數據,而不可重複讀讀到的卻是已經提交的數據,但實際上是違反了事務的一致性原則。

看上圖範例應該蠻直覺的,Transaction A 在同一個事務範圍中讀取同一個欄位卻得到不同的結果,違反了一致性原則。

幻讀 Phantom Read

在 Repeatable Read 隔離級別下(之後會介紹),一個事務可能會遇到幻讀(Phantom Read)的問題。

Transaction A 讀取與搜索條件相匹配的若干行。Transaction B 以插入或刪除行等方式來修改 Transaction A 的結果集合,然後再提交。

解決併發事務一致性難題:隔離層級

其實當你第一次思考這個問題的時候,你心中可能會想到一個解法

那不然…不要併發執行…序列化執行就沒事了…?

這個觀念是對的,它的確可以解決資料不一致的問題,然而這樣的一致性卻是用效能換來的,當你遇到高流量的狀況,這樣系統很可能會直接掛掉的,因此最好的方式是因應不同的情況,在 效能 一致性 間做一個權衡取捨,也就是這裡要介紹的隔離層級。

上圖就是今天要介紹的四種隔離層級,最上面的 read uncommitted 效能最好,但一致性最差,到了最下面的 serializable,變成一致性最好,但效能最差。

Read Uncommitted

這是最低的隔離層級,SELECT 指令可以讀取其他 transaction 尚未 commit 的結果,如果這個尚未被 commit 的結果後來被 rollback 了,就會讀取到被取消的資料,也就是剛剛提過的「髒讀」。不過 UPDATE 等「寫」的操作是不會成功的。

這種隔離層級可能產生:

  • 髒讀
  • 不可重複讀
  • 幻讀

Read Committed

這個隔離層級只允許讀取其他 transaction commit 過的資料,因此可以解決髒讀的問題,不過如果一個 transaction 的兩個 SELECT 語法間有另一個交易 commit 了新資料,會造成第一次讀取與第二次讀取結果不一致的問題,也就是上面介紹過的不可重複讀。

這種隔離層級可能產生:

  • 不可重複讀
  • 幻讀

Repeatable Read

此種隔離層級為 innodb 預設的隔離層級,不會考慮別的 transaction 的修改,同一個 transaction 內,除非自己修改,否則多次 SELECT 的結果都會相同,解決了不可重複讀的問題。

這種隔離層級可能產生:

  • 幻讀

Serializable

在一開始提過,是一個用效能換取一致性的隔離層級,讓所有 transaction 序列化執行,避免併發可能會造成的問題。

Deadlock 死結

小結

這篇文簡單介紹了併發的 transaction 可能會發生的三種問題與它的解法 —四種隔離層級,你可能會好奇隔離層級又是怎麼做到的?其實隔離層級背後運用了一個概念:鎖(Lock),如果對這部分有興趣的讀者再自行研究囉。

--

--