MySQL的四種事務隔離級別SQL標準定義了4類隔離級別,包括了一些具體規則,用來限定事務內外的哪些改變是可見的,哪些是可見的。低階別的隔離級一般支援更高的併發處理,並擁有更低的系統開銷。
一、事務的基本要素(ACID)
1、原子性(Atomicity):事務開始後所有操作,要麼全部做完,要麼全部不做,不可能停滯在中間環節。事務執行過程中出錯,會回滾到事務開始前的狀態,所有的操作就像沒有發生一樣,也就是說事務是一個不可分割的整體。
2、一致性(Consistency):事務開始前和結束後,資料庫的完整性約束沒有被破壞 。比如A向B轉賬,不可能A扣了錢,B卻沒收到。
3、隔離性(Isolation):同一時間,只允許一個事務請求同一資料,不同的事務之間彼此沒有任何干擾。比如A正在從一張銀行卡中取錢,在A取錢的過程結束前,B不能向這張卡轉賬。
4、永續性(Durability):事務完成後,事務對資料庫的所有更新將被儲存到資料庫,不能回滾。
小結:原子性是事務隔離的基礎,隔離性和永續性是手段,最終目的是為了保持資料的一致性。
二、事務的併發問題
1、髒讀:事務A讀取了事務B更新的資料,然後B回滾操作,那麼A讀取到的資料是髒資料(某個事務已更新一份資料,另一個事務在此時讀取了同一份資料,由於某些原因,前一個RollBack了操作,則後一個事務所讀取的資料就會是不正確的)
2、不可重複讀:事務 A 多次讀取同一資料,事務 B 在事務A多次讀取的過程中,對資料作了更新並提交,導致事務A多次讀取同一資料時,結果 不一致(在一個事務的兩次查詢之中資料不一致,這可能是兩次查詢過程中間插入了一個事務更新的原有的資料)。
3、幻讀:系統管理員A將資料庫中所有學生的成績從具體分數改為ABCDE等級,但是系統管理員B就在這個時候插入了一條具體分數的記錄,當系統管理員A改結束後發現還有一條記錄沒有改過來,就好像發生了幻覺一樣,這就叫幻讀(在一個事務的兩次查詢中資料筆數不一致,例如有一個事務查詢了幾列(Row)資料,而另一個事務卻在此時插入了新的幾列資料,先前的事務在接下來的查詢中,就會發現有幾列資料是它先前所沒有的)。
小結:不可重複讀的和幻讀很容易混淆,不可重複讀側重於修改,幻讀側重於新增或刪除。解決不可重複讀的問題只需鎖住滿足條件的行,解決幻讀需要鎖表
三、四種隔離級別介紹
Read Uncommitted(讀取未提交內容)
在該隔離級別,所有事務都可以看到其他未提交事務的執行結果。本隔離級別很少用於實際應用,因為它的效能也不比其他級別好多少。讀取未提交的資料,也被稱之為髒讀(Dirty Read)。
Read Committed(讀取提交內容) 在設定二進位制日誌格式時,建議使用row格式
它滿足了隔離的簡單定義:一個事務只能看見已經提交事務所做的改變。這種隔離級別 也支援所謂的不可重複讀(NonrepeatableRead),因為同一事務的其他例項在該例項處理其間可能會有新的commit,所以同一select可能返回不同結果。
Repeatable Read(可重讀)
這是MySQL的預設事務隔離級別,它確保同一事務的多個例項在併發讀取資料時,會看到同樣的資料行。不過理論上,這會導致另一個棘手的問題:幻讀 (Phantom Read)。簡單的說,幻讀指當使用者讀取某一範圍的資料行時,另一個事務又在該範圍內插入了新行,當使用者再讀取該範圍的資料行時會發現有新的“幻影” 行。InnoDB和Falcon儲存引擎通過多版本併發控制(MVCC,Multiversion Concurrency Control)機制解決了該問題。
Serializable(可序列化)
這是最高的隔離級別,它通過強制事務排序,使之不可能相互衝突,從而解決幻讀問題。簡言之,它是在每個讀的資料行上加上共享鎖。在這個級別,可能導致大量的超時現象和鎖競爭。
四、操作演示
檢視一下Mysql版本
檢視InnoDB儲存引擎 系統級的隔離級別 和 會話級的隔離級別,預設為repeatable-read
select @@global.tx_isolation,@@tx_isolation;
1、讀未提交:
(1)開啟一個客戶端A,並設定當前事務模式為read uncommitted(未提交讀),查詢表user的初始值:
(2)在客戶端A的事務提交之前,開啟另一個客戶端B,更新表user:
(3)這時,雖然客戶端B的事務還沒提交,但是客戶端A就可以查詢到B已經更新的資料:
(4)一旦客戶端B的事務因為某種原因回滾,所有的操作都將會被撤銷,那客戶端A查詢到的資料其實就是髒資料:
要想解決這個問題可以採用讀已提交的隔離級別
2、讀已提交
(1)開啟一個客戶端A,並設定當前事務模式為read committed(未提交讀),查詢表user的初始值:
(2)在客戶端A的事務提交之前,開啟另一個客戶端B,更新表user:
(3)這時,客戶端B的事務還沒提交,客戶端A不能查詢到B已經更新的資料,解決了髒讀問題
(4)客戶端B的事務用COMMIT命令提交之後,再檢視一下客戶端A的資料
(5)客戶端A執行與上一步相同的查詢,結果 與上一步不一致,即產生了不可重複讀的問題,在應用程式中,假設我們處於客戶端A的會話,查詢到id為1的amount為5000,但是其他事務將id為1的amount值改為4900,我們並不知道,如果用4900這個值去做其他操作,是有問題的,不過這個概率真的很小,要想避免這個問題,可以採用可重複讀的隔離級別。
3、可重複讀
(1)開啟一個客戶端A,並設定當前事務模式為repeatable read,查詢表user的初始值:
(2)在客戶端A的事務提交之前,開啟另一個客戶端B,更新表user並提交,客戶端B的事務居然可以修改客戶端A事務查詢到的行,也就是mysql的可重複讀不會鎖住事務查詢到的行,sql標準中事務隔離級別為可重複讀時,讀寫操作要鎖行的,mysql居然沒有鎖,。在應用程式中要注意給行加鎖,不然你會以步驟(1)中id為1的amount為5000作為中間值去做其他操作。
(3)在客戶端A執行步驟(1)的查詢:
(4)執行步驟(1),id為1的amount仍然是50000與步驟(1)查詢結果一致,沒有出現不可重複讀的 問題;接著執行update user set amount = amount -100 where id = 1,amount沒有變成5000-100 = 4900,id為1的amount值用的是步驟(2)中的4900來算的,所以是資料為4800,資料的一致性倒是沒有被破壞!
(5) 在客戶端A開啟事務,查詢表user的初始值
(6)在客戶端B開啟事務,新增一條資料,其中amount欄位值為8000,並提交
(7) 在客戶端A計算amount之和,值為4800+3500+6700=15000,沒有把客戶端B的值算進去,客戶端A提交後再計算amount之和,居然變成了23000,這是因為把客戶端B的8000算進去了,站在客戶的角度,客戶是看不到客戶端B的,它會覺得是天下掉餡餅了,多了8000塊,這就是幻讀,站在開發者的角度,資料的 一致性並沒有破壞。但是在應用程式中,我們得程式碼可能會把15000提交給使用者,如果你一定要避免這情況小概率狀況的發生,那麼就要採取下面要介紹的事務隔離級別“序列化”
4、序列化
(1)開啟一個客戶端A,並設定當前事務模式為serializable,查詢表user的初始值:
(2)開啟一個客戶端B,並設定當前事務模式為serializable,插入一條記錄報錯,表被鎖了插入失敗,mysql中事務隔離級別為serializable時會鎖表,因此不會出現幻讀的情況,這種隔離級別併發性極低,往往一個事務霸佔了一張表,其他成千上萬個事務只有乾瞪眼,得等他用完提交才可以使用,開發中很少會用到。
補充:
1、mysql中預設事務隔離級別是可重複讀時並不會鎖住讀取到的行
2、事務隔離級別為序列化時,讀取資料會鎖住整張表
3、閱讀此文時,如果站在開發者的角度,也許會覺得不可重複讀和幻讀,在邏輯上並沒有什麼問題,最終資料仍然是一致的,但是站在使用者的角度,他們通常只能看到一個事務(只能看到客戶端A,不知道客戶端B這個臥底的存在),而不會考慮事務併發執行的現象,一旦出現同一資料多次讀取結果不同,或者憑空出現新記錄,他們可能會產生疑慮,這是使用者體驗的問題。
4.事務在mysql中執行時,最終的結果不會出現資料的一致性的問題,因為在一個事務中,mysql執行某個操作未必會使用前一個操作的中間結果,它會根據其他併發事務的實際情況採來處理,看起來不合邏輯,但是保證了資料的一致性 ;但是事務在應用程式中執行時,一個操作的結果會被下一個操作用到,並進行其他的計算。這是我們得小心,可重複讀的時候應該鎖行,序列化時 要鎖表,不然會破壞資料的一致性。
5、事務在mysql中執行時,mysql會根據各個事務的實際情況綜合處理,導致資料的一致性沒有被破壞,但是應用程式時按照邏輯套路來出牌,並沒有mysql聰明,難免會出現資料的一致性問題。
6、隔離級別越高,越能保證資料的完整性和一致性,但是對併發效能的影響也越大,魚和熊掌不可兼得啊。對於多數應用程式,可以優先考慮把資料庫系統的隔離級別設為Read Committed,它能夠避免髒讀取,而且具有較好的併發效能。儘管它會導致不可重複讀、幻讀這些併發問題,在可能出現這類問題的個別場合,可以由應用程式採用悲觀鎖或樂觀鎖來控制。