解析MySQL事務隔離級別
事務隔離是分散式系統對一致性保證的重要機制,是保證ACID的重要基礎設施。 MySQL InnoDB事務隔離級別官方說明 中對MySQL的事務隔離機制有詳細介紹。
結合示例,本文對 事務隔離級別
相關術語進行解析。
MySQL四類事務隔離級別
事務隔離級別從高到低為:
- READ UNCOMMITTED :未提交讀
- 讀取未提交內容,所有事務可看到其他未提交事務的結果,很少實際使用
- 讀取未提交的資料稱為髒讀(Dirty Read)
- READ COMMITTED :提交讀
- 多數資料庫的預設隔離級別(MySQL預設不是,預設為REPEATABLE-READ)
- 滿足隔離的簡單定義: 一個事務只能看到已提交事務所做的改變
- 這種隔離級別,支援所謂的不可重讀(Non-repeatable Read),同一事務的其他例項在該例項過程中可能有新commit,所以同一個select可能返回不同結果( 同一個事務如何做到其他例項? )
- REPEATABLE READ :重複讀
- 可重複讀(MySQL預設事務隔離),但可能出現幻讀(Phantom Read)
-
幻讀(Phantom Read)
:當用戶讀取某範圍資料行時,另一事務在此範圍內 插入新行 ,當用戶再次讀取此範圍資料行時,讀取到新的幻影行 - InnoDB通過多版本併發控制MVCC機制解決該問題
- SERIALIZABLE :序列化
- 最高隔離級別,強制事務排序(序列化),不會互相沖突
- 每個讀資料航增加共享鎖
- 此級別,可能導致大量超時現象和鎖競爭
按事務隔離級別來說,級別越低資料一致性保障效果越差,而併發能力則越強。(一致性VS併發性是天然矛盾體)
髒讀、不可重複讀、幻讀
上述四種隔離級別採用不同的鎖實現,對應級別下可能發生問題:
- 髒讀(Dirty Read) :某事務已更新一份資料,而另一個事務此時讀取了同一份資料,某些原因前一個更新做了回滾Rollback操作,則後一個事務資料是不正確的(讀到了髒的資料)
- 不可重複讀(Non-repeatable read) :在 一個事務的兩次查詢 中資料不一致,可能是兩次查詢過程中另一個事務更新了資料。
- 幻讀(Phantom Read) :一個事務的兩次查詢中資料不一致。例如一個事務查詢資料,而另一個事務卻插入新的資料,先前事務的查詢中,發現一些資料是之前查詢中沒有的
注意:根據 ANSI SQL Standard
在可重複讀級別下允許出現幻讀,MySQL實現滿足標準,這不是Bug。但PostgreSQL在可重複讀時不會出現幻讀問題,這是不同引擎實現機制上的差異。
隔離級別 | 髒讀 | 不可重讀 | 幻讀 |
---|---|---|---|
讀未提交(Read Uncommitted) | yes | yes | yes |
讀已提交(Read Committed) | no | yes | yes |
可重複讀(Repeatable Read) | no | no | yes |
可序列化(Searializable) | no | no | no |
示例1:髒讀
-- 會話1 SET SESSION TRANSACTION ISOLATION LEVEL read uncommitted; -- 設定會話隔離級別為 未提交讀 start transaction; select * from xxx; -- 此時為空,事務未提交 -- 會話2 start transaction; insert into xxx values(1); -- 未提交事務中,插入資料 -- 會話1 select * from xx; -- 讀取到會話2中未提交資料,這被稱為 髒讀
髒讀(Dirty Read)
是對一致性有要求的情況下無法接受的,所有 未提交讀
在實際應用場景中幾乎很少使用。
示例2:不可重複讀
-- 會話1中操作: start transaction; select * from xxx where id=1; -- 此時資料狀態為a -- 注意此會話中開啟事務,未提交 -- 切換至會話2操作 update xxx set xxx=newValue where id=1; -- 更新資料至新的狀態b -- 再次切換至會話1操作 select * from xxx where id=1; -- 此時查詢出的資料狀態就有兩種選擇:新狀態b、老狀態a -- 所謂的連續讀:同一個事務中的兩次讀操作,資料狀態保持一致
結論:
read committed repeatable read
操作順序 | 會話1 | 會話2 |
---|---|---|
1 | start transaction;select * from xxx where id=1; |
|
2 | update xxx set xxx=newValue where id=1; |
|
3 | select * from xxx where id=1; |
|
結果: read committed 級別 |
newValue新狀態資料(此時為 不可重複讀 問題) |
|
結果: repeatable read 級別 |
原狀態資料,滿足 重複讀 要求 |
示例3:幻讀
-- 會話1 start transaction; select * from xxx; -- 此時查詢表為空,且事務未提交 -- 會話2 start transaction; insert into xxx values(1); -- 新增一條記錄 commit; -- 會話1 select * from xxx; -- 此時查詢表仍為空,表示滿足[可重複讀]特性 update xxx set age=99 where id=1; -- 更新會話2中插入記錄(此時會話1並不可見) Query OK, 1 row affected Rows matched: 1Changed: 1Warnings: 0 -- 更新1條記錄,隱約感覺不安 select * from xxx; -- 再次讀取時,竟然讀取到內容(前一次讀取時為空,2次讀取時讀取到內容),出現`幻讀` commit;
操作順序 | 會話1 | 會話2 |
---|---|---|
1 | start transaction;select * from xxx where id=1; –空表 |
|
2 | insert into xxx values(1); |
|
3 | select * from xxx; – 新插入元素對會話1中查詢不可見,滿足 可重複讀 |
|
4 | update xxx set age=99 where id=1;select * from xxx; – 新插入元素在會話1竟然可以被成功更新, 再次讀時 讀取到新內容,復現 幻讀 問題 |
|
結果: repeatable read 級別時 |
一個事務中,兩次讀操作,第二次讀時發現了首次讀時不存在的內容,這被稱為 幻讀 問題 |
附錄:事務相關SQL命令
-
設定自動提交
-- 取消autocommit set aucommit=0 show variables like "%autocommit%";
-
檢視隔離級別
-- 檢視隔離級別 SELECT @@global.tx_isolation; SELECT @@session.tx_isolation; SELECT @@tx_isolation; -- 三個角度的隔離:全域性、會話、事務隔離 show VARIABLES like "%iso%"; +---------------+-----------------+ | Variable_name | Value| +---------------+-----------------+ | tx_isolation| REPEATABLE-READ | +---------------+-----------------+ show global variables like '%iso%'; +---------------+-----------------+ | Variable_name | Value| +---------------+-----------------+ | tx_isolation| REPEATABLE-READ | +---------------+-----------------+
-
設定事務隔離級別
SET SESSION TRANSACTION ISOLATION LEVEL read uncommitted; SET SESSION TRANSACTION ISOLATION LEVEL read committed; SET SESSION TRANSACTION ISOLATION LEVEL repeatable read; SET SESSION TRANSACTION ISOLATION LEVEL serializable;
-
事務操作
-- 事務中一次讀操作 start transaction; SELECT * FROM text.tx; commit; -- 事務中回滾操作 start transaction; SELECT * FROM text.tx; update text.tx set num =10 where id = 1; insert into text.tx(id,num) values(9,9); rollback; commit;