mysql事務隔離級別/髒讀/不可重複讀/幻讀詳解
一、四種事務隔離級別
1.1read uncommitted 讀未提交
即:事務A可以讀取到事務B已修改但未提交的資料。
除非是文章閱讀量,每次+1這種無關痛癢的場景,一般業務系統沒有人會使用該事務隔離級別,標準實在太寬鬆了。
1.2read committed 讀已提交(簡稱RC)
即:事務A只能讀取到事務B修改並已提交的資料。
這個級別相對要嚴格一些,至少是要等其它事務把變更提交到db,才能讀取到,聽上去蠻靠譜的。但是有些業務場景,比如會員系統中,如果要在一個事務中,多次讀取使用者身份,判斷是否會員,如果剛開始讀取到該使用者是會員,做了一些邏輯處理,後面又讀到使用者不是會員了,這就有點崩潰,不知道如何繼續。這種希望同1個事務中,關鍵資料不管讀取多次次,結果都一樣,RC級別就不行了。
1.3repeatable read 可重複讀
即:同一個事務中,多次讀取某一行記錄,始終是一樣的值,不管在此期間,其它事務有沒有修改過該資料(不論是否提交)。該級別解決了RC不可重複讀的問題,但是存在幻讀問題(幻讀後面會詳解)。
1.4serializable 序列化
即:一個事務在修改其它資料時,如果有其它事務也想改,必須等前面的事務提交或回滾後,才能繼續。最嚴格的級別,但是效能最低,也幾乎沒人用。
二、髒讀/不可重複讀/幻讀
2.1髒讀
驗證:
a. 找一個mysql環境,建一個測試表t_people,就2列 id ,name
b. 開二個mysql終端,連到db上,為方便講解,這2個終端稱為“終端1”、“終端2”,終端1裡輸入:
|
即:設定當前會話的隔離級別為"讀未提交"。
終端2裡,輸入:
1 2 |
|
然後再回到“終端1”,執行
1 |
|
可以看到,讀取到了未提交的髒資料 。終端2裡,此時如果執行rollback回滾
終端1裡,繼續執行
1 |
name from t_people where id=1; |
可以發現最新結果,已經是回滾後的資料。很顯然:如果有髒讀問題出現,就更加保證不了“可重複讀”。
2.2不可重複讀
將事務隔離級別設定成read committed(即:讀已提交),可解決髒讀問題,但滿足不了“可重複讀需求”。
驗證方法跟剛才類似,終端1裡輸入:
1 |
|
將級別設定成RC,然後2個終端裡都開啟事務,終端2中,修改一行資料,但是不提交,此時終端1裡應該是讀不到終端2修改的資料。然後終端2提交,終端1才能讀到修改後的資料。終端2如果繼續修改、提交,終端1裡再讀取這1行,將是最新的值。(也就是隻說,只要終端2不斷修改,不斷提交,終端1裡就能讀到這行不同的新值,即:保證不了同1個事務中,同一行資料,多次重複讀取的值不變)
2.3幻讀
將隔離級別繼續調整至Repeatable Read,還是剛才的場景,變成這樣:
事務A對於同一行資料,不管讀多少次,始終是相同的值,完全不理會有沒有其它事務在修改它。有點:“兩耳不聞窗外事,一心只讀聖賢書”的味道。但是這也有問題,比如秒殺訂單系統中,事務A第1次讀取商品庫存,發現還有1個,可以下單,趕緊繼續,但是此時,可能有另一個事務,也在下單,已經提交了訂單,把庫存減為0了,事務A並不知道,因為多次讀取庫存的值是一樣的,還是1,最後仍然把訂單建立了,形成超賣。
驗證方法:
1 |
|
剩下的步驟跟前面類似,就不重複贅述了。
2.4序列化
從db層面,要想同時解決髒讀、不可重複讀、幻讀,只有序列化這個級別可以做到。
1 |
|
如下圖:終端1設定序列化後,緊接著select xxx where id=1這條語句後,id=1的這行記錄,就被鎖了。
在終端2裡,更新其它記錄(即:id不等於1)可以正常成功,但是更新id=1 時,就會卡住,除非終端1把事務提交或回滾,否則將一直卡著,直到超時失敗。
小結:
隔離級別 | 存在的問題 |
讀未提交 | 髒讀、不可重複讀、幻讀 |
讀已提交 | 不可重複讀、幻讀 |
可重複讀 | 幻讀 |
序列化 | 效能問題 |
隔離級別越嚴格,db綜合性能越低。
建議:
大多數情況下,RC(讀已提交)基本上就足夠了,如果併發度高,可以考慮“RC級別+(應用層)分散式鎖”,這樣即能保證資料正確,對db的效能壓力也較低。