MySQL中事務隔離級別
最近在學習資料庫的事務隔離級別。在這裡整理一下。由於本人水平和寫作能力有限,如文中有錯誤或者表達不清楚的地方,請多包涵。有任何意見或建議,歡迎留言。
我們都知道關係型資料庫事務有 ACID
的原則,他們分別代表原子性(Atomicity),一致性(Consistency)、隔離性(Isolation)和永續性(Durability)。我們今天重點了解一下隔離性。(注:本文所有依據都是根據MySQL 5.7.30)
1. 資料庫事務併發會操作出現的問題
在詳細討論這幾個隔離級別之前,我們有必要先了解一下多個事務併發操作時會出現的異常情況:
dirty read
(髒讀。讀取到其他事物未提交的資料,這種操作是非常危險的,因為其他事物未提交的資料可能會被回滾或者進一步更新,這種操作不遵循ACID原則。)non-repeatable read
(不可重複讀。在查詢資料時,同一個事務多次查詢本應返回相同的資料,卻出現了不一致的情況。MySQL認為,這種操作違背了資料庫設計的ACID原則。在事務中,資料應該是一致的,具有可預測和穩定的關係。)phantom read
(幻讀。在查詢資料時,一個查詢在一個事務中執行多次,在這期間有其他的事務插入了新行或更新行,導致多次執行的結果不一致。)
2. MySQL中提供的隔離級別
事務隔離的作用在於多個事務併發操作時,事務之間相互隔離,互不影響。MySQL中提供了全部四種(這四種隔離級別是在SQL 1992標準中定義的)隔離級別:READ-UNCOMMITTED
READ-COMMITTED
,REPEATABLE-READ
和 SERIALIZABLE
。下面分別介紹一下每一種隔離級別的特點、實現方式以及可能會出現的異常情況。我們可以通過下面的程式碼檢視並臨時設定當前連線會話的隔離級別:
mysql> select @@session.tx_isolation; -- 檢視當前會話的隔離級別 +------------------------+ | @@session.tx_isolation | +------------------------+ | READ-UNCOMMITTED | +------------------------+ 1 row in set (0.13 sec) mysql> set @@session.tx_isolation='READ-UNCOMMITTED'; --設定隔離級別為 READ-UNCOMMITTED Query OK, 0 rows affected (0.10 sec)
-
READ-UNCOMMITTED
允許讀取其他事務未提交的資料,我們可以通過下面的執行結果看到,事務1把使用者的餘額減少500,在未提交的情況下,事務2可以查詢到資料的更新。這種情況是非常危險的,因為其他事務未提交的資料是不可靠的,如果當前事務拿到了這個資料進行使用,將導致不可預料的後果。所以在該級別下,會出現髒讀。
既然該級別下,連其他事務未提交的資料都可以讀取,那肯定已提交的資料也是可以讀取的,所以也會出現不可重複度。
再看一下,如果在執行過程中,其他事務插入了資料的情況。發現同樣也是可以讀取的,所以在該級別下,也會出現幻讀。
總結,在READ-UNCOMMITTED
級別下,讀取的資料是不可靠並且不一致的,會出現髒讀,不可重複度和幻讀。 -
READ-COMMITTED
先看一下是否能讀取到其他事務未提交的資料,發現沒有問題。不會出現髒讀。
接著看看會不會出現不可重複讀的問題呢。從下面的執行結果可以看出,不可重複讀的問題,還是存在的。
幻讀問題也同樣存在。
總結,在READ-COMMITTED
級別下,髒讀問題已經被解決,不可重複度和幻讀依然存在。 -
REPEATABLE-READ
在MySQL的InnoDB
引擎中,REPEATABLE-READ
是預設的隔離級別。我們分別測試一下上面的三個問題。首先是髒讀,我們可以從下面的結果中看到,髒讀不會出現。
從這個隔離級別的名字就可以看出,可重複讀。那我們來測試一下會不會出現不可重複讀的問題呢。下圖中的結果表示,不會出現。
再看一下幻讀,從下圖中的結果可以看到,幻讀問題也被解決了。
總結,在REPEATABLE-READ
級別下,髒讀、不可重複度和幻讀問題都已經被解決。 -
SERIALIZABLE
在SERIALIZABLE
級別下,事務是按照順序執行的。我們來測試一下是否會出現上面的幾個問題,但在修改資料時,直接出錯了
得到錯誤1205 - Lock wait timeout exceeded; try restarting transaction
,等待鎖超時。這是因為我們開啟了兩個事務,從SERIALIZABLE
的字面意思可以看出,序列化,應該是說事務要一個一個的執行,兩個事務不能同時執行,但為什麼前面的查詢語句沒問題呢? 我們前面開啟了兩個事務,同時去查詢一條資料是可以的。 這是因為,在該級別下,如果自動提交(autocommit
)是關閉的,InnoDB引擎會隱式的把所有的SELECT
語句轉換為SELECT ... LOCK IN SHARE MODE
,我們寫的查詢語句被轉換成了共享鎖查詢,所以兩個查詢語句是可以同時執行的,而修改會被阻塞。那麼,又有另一個問題,如果自動提交是開啟的,查詢語句和修改語句可以同時執行嗎? MySQL認為,如果自動提交是開啟的,那麼一條查詢語句就在它自己的事務裡,因此它肯定是隻讀的,所以它不會阻塞其他事務。
總結:由於在該模式下,一個(非自動提交的)事務的查詢會被隱式的轉換為共享鎖查詢,會阻塞其他事務修改該資料。所以不會出現髒讀、不可重複讀以及幻讀。
3. MySQL中四個隔離級別和三個併發問題的關係
- | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|
READ-UNCOMMITTED |
✅ | ✅ | ✅ |
READ-COMMITTED |
❎ | ✅ | ✅ |
REPEATABLE-READ |
❎ | ❎ | ❎ |
SERIALIZABLE |
❎ | ❎ | ❎ |
4. MySQL中事務隔離級別的實現原理
MySQL中提供了一種一致性非鎖定讀取(Consistent Nonlocking Reads
),一致性讀取意味著InnoDB使用多版本控制,查詢將指向某個時間點的資料庫快照。查詢會看到在該時間點之前已提交的更改,但不會看到該時間點之後的更改以及未提交的事務的更改。但是有一個例外情況,在該事務中如果對資料進行了更改,查詢操作將可以檢視到這些資料的最新版本(可能在該事務的時間點之後,被其他事務修改過),而其他資料保持時間點之前的舊版本,此時,在該事務中,可能會看到該表處於一種從未存在過的狀態。
如果事務隔離級別是 REPEATABLE-READ
,那麼同一事務中的所有一致性查詢都將讀取這樣的查詢第一次查詢生成的快照。在進行第一次查詢時,InnoDB會給當前事務一個時間點,事務根據這個時間點查詢資料庫。如果其他事務在該時間點之後進行了資料更新,則當前事務不會看到這些更新。可以通過提交當前事務,然後再發出新的查詢,獲得最新的快照。
如果事務隔離級別是 READ-COMMITTED
,那麼事務中每一次查詢,都將設定並讀取自己最新的快照。
一致性讀取,是InnoDB在 READ-COMMITTED
和 REPEATABLE-READ
級別下處理普通查詢(沒有加鎖的查詢)的預設模式。一致性讀取不會對其訪問的表設定任何鎖,因此,在對錶進行一致性讀取時,其他事務可以自由的修改這些表。
下面我們分析一下 REPEATABLE-READ
和 READ-COMMITTED
的實現原理。由於 READ-UNCOMMITTED
和 READ-COMMITTED
的區別只是在於能否讀取到其他事務未提交的更改,所以這兩個級別是類似的。SERIALIZABLE
在前面已經說過了,所以這裡不再贅述。
REPEATABLE-READ的實現原理
在該級別下,普通查詢會使用一致性查詢,同一個事務中的後續查詢都將讀取第一次查詢時建立的快照。所以可以保證同一事物中的查詢的一致性。
如果使用鎖定查詢(SELECT ... FOR UPDATE
或者 SELECT ... LOCK IN SHARE MODE
)和 UPDATE
語句、 DELETE
語句,鎖定取決於該查詢是否有唯一搜索條件的唯一索引,還是使用範圍型別的搜尋條件。
- 存在唯一索引
InnoDB只鎖定找到的索引記錄。 - 範圍型別的條件
InnoDB鎖定掃描到的索引範圍。
READ-COMMITTED的實現原理
在該級別下,和 REPEATABLE-READ
一樣,普通查詢會使用一致性查詢。不同的是,同一個事務中,所有的一致性查詢都會設定並讀取新的快照。這樣可以讓事務在每一次讀取時,都可以檢視到其他事物已經提交的更改。但同樣也帶來了不可重複讀和幻讀的問題。
使用鎖定查詢和 UPDATE
語句、DELETE
語句,鎖定的範圍和 REPEATABLE-READ
一致,不再贅述。
5. 其他問題
現在我們都知道在 READ-UNCOMMITTED
級別下,能夠讀取其他事務未提交的資料。那麼能夠對這些資料進行更改從而導致資料不一致嗎?看下圖的執行結果
這裡我們得到了一個錯誤 1205 - Lock wait timeout exceeded; try restarting transaction
,從錯誤資訊看,是在等待鎖的過程中超時了。可以看出,在該級別下,MySQL也是使用了鎖的。雖然在這種模式下可以讀取到其他事務未提交的資料,但不允許併發修改同一條資料。READ-UNCOMMITTED
除了可以讀取其他事務未提交的資料之外,其他的工作方式與 READ-COMMITTED
類似,而在 READ-COMMITTED
級別下,使用鎖定查詢、UPDATE
語句、DELETE
語句,都是會鎖定資料的。
參考資料:
https://dev.mysql.com/doc/refman/5.7/en/glossary.html
https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html