(三)事務的併發控制

一、事務的隔離級別

三級封鎖協議的數據庫理論反映在實際的數據庫系統上,就是四級事務隔離機制。

讀未提交(READ UNCOMMITTED)

在Read Uncommitted策略下,數據庫遵循一級封鎖協議(修改須加X鎖互斥),只對修改數據的併發操作做限制。對於沒有修改併發的操作沒有做出限制——一個事務不能修改其他事務正在修改的數據,但可以讀取到其他事務中尚未提交的修改,這些修改如果未被提交,將會成爲髒數據。

讀已提交(READ COMMITTED)

在Read committed策略下,數據庫遵循二級封鎖協議(讀完立即釋放S鎖),只允許讀取已經被提交的數據,反過來講,如果一個事務修改了某行數據且尚未提交,而第二個事務要讀取這行數據的話,那麼是不允許的。在MySql的InnoDB下,雖然這種操作不被允許,但MySQL不會阻塞住數據的查詢操作,而是會查詢出數據被修改之前的備份,返回給客戶端。MySQL的這種機制稱爲MVCC(多版本併發控制),就是說數據庫在事務併發的過程中對數據維護多個版本,使得不同的事務對不同的數據版本進行讀寫。這樣的機制反映在應用中就是,在任何時候對數據庫查詢總是可以得到數據庫中最近提交的數據。未被提交的事務的數據被隔離起來,無法被查詢到,即防止髒讀發生。

可重複讀(REPEATABLE READ)

Repeat Read又比Read Committed更加嚴格一點,但仍然是在二級封鎖協議(讀完立即釋放S鎖)的範疇,只是讀取過程受到更多MVCC的影響。在Read Committed下,允許一個事務中多次相同查詢得到不同的結果,就是所謂的不可重複讀問題。這在一些應用中是允許的,所以oracle、SQL server上默認這一隔離級別,但MySQL沒有,它默認Repeat Read級別。在這一級別下,有賴於MVCC,同一個事務中的查詢只能查到版本號不高於當前事務版本的數據,即事務只能看到該事務開始前或者被該事物影響的數據。反過來說,這一級別下,不允許事務讀取在該事務開始後新提交的數據。即防止了不可重複讀的發生
依靠上面的機制,已經做到了在事務內數據內容的不變,但是不能保證多次查詢得到的數據數量一致。因爲在一個事務執行的過程中別的事務完全可以執行數據插入,當插入了剛好符合查詢條件的數據時,就會引發數據查詢結果集增加,引發幻讀。還有一種情況就是,如果一個事務想插入一條數據,而另一個事務已經插入了含有相同主鍵的數據,那麼當前事務也會被阻塞,並最終執行失敗,雖然當前事務根本無法查詢到這一條數據,這也是一種幻讀。InnoDB提供的間隙鎖機制可以在一定程度上防止幻讀的發生。

可串行化(SERIALIZABLE)

最後,最強事務隔離機制Serializable,它遵循三級封鎖協議(事務結束再釋放S鎖),使得所有的事務必須串行化執行,只要有事務在對錶進行查詢,那麼在此事務提交前,任何其他事務的修改都會被阻塞。這解決了一切併發問題,但會造成大量的等待、阻塞甚至死鎖,使系統性能降低。

要注意,在任何一種隔離機制下,都是不允許一個事務刪除或修改另一個事務影響過而未提交的數據的。因爲事務增、刪、改數據以後,會在該行加上排它鎖,排它鎖會阻塞其他事務再次對該行數據操作。也正是由於排它鎖的存在,這四種隔離機制都不會出現任何一種更新丟失的現象,因爲一條信息根本不允許第二個事務進行修改。

隔離級別 髒讀 不可重複讀 幻影讀
未提交讀 YES YES YES
提交讀 NO YES YES
可重複讀 NO NO YES
可串行化 NO NO NO

二、多版本併發控制

多版本併發控制(Multi-Version Concurrency Control, MVCC)是 MySQL 的 InnoDB 存儲引擎實現隔離級別的一種具體方式,用於實現提交讀和可重複讀這兩種隔離級別。

而未提交讀隔離級別總是讀取最新的數據行,無需使用 MVCC。
可串行化隔離級別需要對所有讀取的行都加鎖,單純使用 MVCC 無法實現。

版本號

  • 系統版本號:是一個遞增的數字,每開始一個新的事務,系統版本號就會自動遞增。
  • 事務版本號:事務開始時的系統版本號。

InooDB 的 MVCC 在每行記錄後面都保存着兩個隱藏的列,用來存儲兩個版本號:

  • 創建版本號:指示創建一個數據行的快照時的系統版本號;
  • 刪除版本號:如果前事務版本號大於等於該快照的刪除版本號, 表示該快照已經被刪除了。

Undo 日誌

InnoDB 的 MVCC 使用到的快照存儲在 Undo 日誌中,該日誌通過回滾指針把一個數據行(Record)的所有快照連接起來。
這裏寫圖片描述

實現過程

以下實現過程針對可重複讀隔離級別。

1. SELECT

當開始新一個事務時,該事務的版本號肯定會大於當前所有數據行快照的創建版本號,理解這一點很關鍵。

多個事務必須讀取到同一個數據行的快照,並且這個快照是距離現在最近的一個有效快照。但是也有例外,如果有一個事務正在修改該數據行,那麼它可以讀取事務本身所做的修改,而不用和其它事務的讀取結果一致。

小於等於創建版本不能讀:把沒有對一個數據行做修改的事務稱爲 T,如果T的版本號小於等於所要讀取的數據行快照的創建版本號, 說明有其他事務動了創建版本,說明該數據行快照是其它事務的最新修改,因此不能去讀取它。

大於等於刪除版本不能讀:除了上面的要求,如果T的版本號 大於等於 T 所要讀取的數據行快照的刪除版本號,說明該數據行快照是已經被刪除的,不應該去讀取它。

2. INSERT

將當前系統版本號作爲數據行快照的創建版本號。

3. DELETE

將當前系統版本號作爲數據行快照的刪除版本號。

4. UPDATE

將當前系統版本號作爲更新前的數據行快照的刪除版本號,並將當前系統版本號作爲更新後的數據行快照的創建版本號。可以理解爲先執行 DELETE 後執行 INSERT。

快照讀與當前讀

1. 快照讀

InnoDB提供了非鎖定讀:不需要等待訪問行上的鎖釋放,讀取行的一個快照。
使用 MVCC 讀取的是快照中的數據,這樣可以減少加鎖所帶來的開銷。

select * from table …;

2. 當前讀

讀取的是最新的數據,需要加鎖。以下第一個語句需要加 S 鎖,其它都需要加 X 鎖。

select * from table where ? lock in share mode;
select * from table where ? for update;
insert;
update;
delete;

三、Next-Key Locks

Next-Key Locks 是 MySQL 的 InnoDB 存儲引擎的一種鎖實現。

MVCC 不能解決幻讀的問題,Next-Key Locks 就是爲了解決這個問題而存在的。在可重複讀(REPEATABLE READ)隔離級別下,使用 MVCC + Next-Key Locks 可以解決幻讀問題。

Record Locks

鎖定一個記錄上的索引,而不是記錄本身。

如果表沒有設置索引,InnoDB 會自動在主鍵上創建隱藏的聚集索引,因此 Record Locks 依然可以使用。

Gap Locks(間隙鎖)

鎖定索引之間的間隙,但是不包含索引本身。例如當一個事務執行以下語句,其它事務就不能在 t.c 中插入 15。

SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;

Next-Key Locks

它是 Record Locks 和 Gap Locks 的結合,不僅鎖定一個記錄上的索引,也鎖定範圍內的索引。例如一個索引包含以下值:10, 11, 13, and 20,那麼就需要鎖定以下區間(左開右閉):

(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章