事務與MVCC

你的益達_發表於2019-04-15

前言

關於事務,是一個很重要的知識點,大家在面試中也會被經常問到這個問題;

資料庫事務有不同的隔離級別,不同的隔離級別對鎖的使用是不同的,鎖的應用最終導致不同事務的隔離級別;在上一篇文章中我們說到了資料庫鎖的一部分知識,知道了InnoDB是支援行鎖的,但是走行鎖是基於索引的;

這裡我們會說一下和鎖緊密相關的事務;

希望本文對大家有所幫助;

引入

本文參考文章:資料庫的兩大神器

事務和MVCC

資料庫事務有不同的隔離級別,不同的隔離級別對鎖的使用是不同的,鎖的應用最終導致不同事務的隔離級別

關於事務,大家也是比較熟悉的,在這裡我們再來嘮叨一下:

說到事務,就不得不提它的特性以及隔離級別了;

特性

事務具有四個特性:原子性、一致性、隔離性、永續性。這四個屬性通常被稱為ACID屬性。

  • 原子性(Atomicity :事務作為一個整體被執行,包含在其中的對資料庫的操作要麼全部被執行,要麼都不執行。
  • 一致性(Consistency:事務應確保資料庫的狀態從一個一致狀態轉變為另一個一致狀態。一致狀態的含義是資料庫中的資料應滿足完整性約束。
  • 隔離性(Isolation:多個事務併發執行時,一個事務的執行不應影響其他事務的執行。
  • 永續性(Durability:一個事務一旦提交,他對資料庫的修改應該永久儲存在資料庫中。

對於以上的四個特性,我們來拿經典的轉賬的例子來說明;

有A和B兩個人,現在A需要往B的賬戶上轉錢,一般的操作是這樣:

  1. A賬戶需要讀取賬戶餘額(500);
  2. A需要給B轉賬100元,所以需要從A的賬戶上扣除100元(500 - 100);
  3. 把減去的結果寫回A賬戶(400);
  4. B賬戶需要讀取賬戶餘額(500);
  5. 對B賬戶進行加的操作(500 + 100);
  6. 把結果寫回B賬戶(600);

以上是轉賬的操作步驟,我們來說明一下事務的四大特性:

原子性

以上的六步操作要麼全部執行,要麼全部不執行。不管執行到那一步出現了問題,就需要執行回滾操作;

一致性

在轉賬之前,A和B的賬戶加一起500 + 500 = 1000 元,在轉賬之後A和B的賬戶加起來是400 + 600 = 1000。也就是說,資料的狀態在執行該事務操作之後從一個狀態改變到了另外一個狀態,需要保持一致性;

隔離性

在A向B轉賬的過程中,只要所處事務還沒有提交,其他事務查詢A或者B賬戶的時候,兩個賬戶的金額都不會發生變化;

如果在A給B轉賬的同時,有另外一個事務執行了C給B轉賬的操作,那麼當兩個事務都結束的時候,B賬戶裡面的錢應該是A轉給B的錢加上C轉給B的錢再加上自己原有的錢;

永續性

一旦轉賬成功,事務提交,所做的修改就會永久的儲存;

參考文章:www.hollischuang.com/archives/89…

隔離級別

我們對於事務的隔離級別也是很清楚的,分為四種:

  • Read uncommitted:未提交讀
    • 最低階別,會出現髒讀、不可重複讀、幻讀。
  • Read committed:已提交讀
    • 避免髒讀,會出現不可重複讀和幻讀。
  • Repeatable read:可重複讀
    • 避免髒讀和不可重複讀,會出現幻讀(在MySQL實現的Repeatable read配合gap鎖不會出現幻讀!)。
  • Serializable :序列化
    • 避免髒讀、不可重複讀、幻讀。

髒讀

在Read uncommitted隔離級別下會出現髒讀,我們先來看一下髒讀;

髒讀:一個事務讀取到另一個事務未提交的資料的情況被稱為髒讀。

舉例說明:

還是拿轉賬的例子作為說明。A向B轉賬,A執行了轉賬語句,但A還沒有提交事務,B讀取資料,發現自己賬戶錢變多了!B跟A說,我已經收到錢了。A回滾事務【rollback】,等B再檢視賬戶的錢時,發現錢並沒有多。

分析:

出現髒讀的本質就是因為操作(修改)完該資料就立馬釋放掉鎖,導致讀的資料就變成了無用的或者是錯誤的資料

解決(Read committed):

從上面的分析也能看出來,解決的方式就是把鎖釋放的位置放到事務提交之後 。這樣的話,在事務還未提交之前,其他的事務對該資料是無法進行操作的,這也是Read committed避免髒讀的做法;

不可重複讀

Read committed 雖然避免了髒讀但是會出現不可重複讀;

不可重複讀:一個事務讀取到另外一個事務已經提交的資料,也就是說一個事務可以看到其他事務所做的修改 ;

舉例說明:

事務A在讀取一條資料,得到結果a,事務B把這條資料改成了b並提交了事務,這個時候事務A再次去讀取這條資料,得到的結果是b。這樣就發生了不可重複讀;

分析:

Read committed 採用的是語句級別的快照!每次讀取的都是當前最新的版本

解決:

Repeatable read避免不可重複讀是事務級別的快照!每次讀取的都是當前事務的版本,即使被修改了,也只會讀取當前事務版本的資料。

這裡涉及到了快照一詞,我們需要說一下這個東西:

MVCC

MVCC(Multi-Version Concurrency Control):多版本併發控制 。通過一定機制生成一個資料請求時間點的一致性資料快照(Snapshot),並用這個快照來提供一定級別(語句級或事務級)的一致性讀取。從使用者的角度來看,好像是資料庫可以提供同一資料的多個版本。一句話總結就是 同一份資料臨時保留多版本的一種方式,進而實現併發控制

快照有兩個級別

  • 語句級
    • 針對於Read committed隔離級別
  • 事務級別
    • 針對於Repeatable read隔離級別

InnoDB MVCC實現分析

InnoDB 的 MVCC, 是通過在每行記錄後面儲存兩個隱藏的列來實現的, 這兩個列,分別儲存了這個行的建立時間,一個儲存的是行的刪除時間。這裡儲存的並不是實際的時間值, 而是系統版本號 (可以理解為事務的 ID),每次開始一個新的事務,系統版本號就會自動遞增當刪除一條資料的時候,該資料的刪除時間列就會存上當前事務的版本號 ;事務開始時刻的系統版本號會作為事務的 ID;

下面看一下在 REPEATABLE READ 隔離級別下, MVCC 具體是如何操作的;

例子

首先建立一個表:

CREATE TABLE `mvcc` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB CHARSET=utf8;
複製程式碼

假設系統版本號從1開始;

INSERT

InnoDB 為新插入的每一行儲存當前系統版本號作為版本號,上面我們假設系統版本號從1開始;

start transaction;
INSERT INTO `mvcc`(username) VALUES ('tom'),('joey'),('James');
commit;
複製程式碼

得到如下結果(後面兩列是隱藏的,通過查詢語句看不到):

id username 建立時間 (事務 ID) 刪除時間 (事務 ID)
1 tom 1 undefined
2 joey 1 undefined
3 james 1 undefined
SELECT

InnoDB會根據以下兩個條件檢查每條記錄:

  • InnoDB只會查詢版本早於當前事務版本的資料行(建立時間系統版本號小於或等於當前事務版本號),這樣可以確保事務讀取到的資料要麼是本次事務開始之前就已經存在的,要麼是當前事務本身做的修改;
  • 行的刪除版本要麼是未定義,要麼大於當前事務的版本號,這樣確保了事務讀取到的行,在事務開始之前未被刪除;

以上兩個條件同時滿足的情況下,才能作為結果返回;

DELETE

InnoDB 會為刪除的每一行儲存當前系統的版本號 (事務的 ID) 作為刪除標識;

具體例子:

第二個事務,系統版本號為2;

start transaction;
select * from mvcc; //step 1
select * from mvcc; //step 2
commit;
複製程式碼

情況一

第三個事務,系統版本號為3;

start transaction;
INSERT INTO `mvcc`(username) VALUES ('yang');
commit;
複製程式碼

當我們執行step 1剛完畢,這個時候第三個事務往表中插入了一條資料,這個時候表中的資料如下:

id username 建立時間 (事務 ID) 刪除時間 (事務 ID)
1 tom 1 undefined
2 joey 1 undefined
3 james 1 undefined
4 yang 3 undefined

然後step 2執行了,得到如下結果:

id username 建立時間 (事務 ID) 刪除時間 (事務 ID)
1 tom 1 undefined
2 joey 1 undefined
3 james 1 undefined

大家可能會感到迷惑,第三個事務不是往裡面插入了一條資料嗎,怎麼查不到。這個時候我們來說一下原因:

  • id = 4是由事務三(系統版本為3)建立的,該資料的建立時間(事務ID)為3;
  • 第二個事務的系統版本號是2,大家要記得我們上面說的查詢的兩個條件;
    • InnoDB只會查詢建立時間(事務ID)小於或等於當前事務的資料行;
    • 查詢刪除時間(事務ID)列大於當前系統版本號的資料行;
  • id = 4的資料的建立時間(事務ID)明顯大於第二個事務的系統版本號,而且刪除時間也是未定義的,所以第三個事務插入的資料未被檢索;

情況二

第四個事務,系統版本為4:

start transaction;  
delete from mvcc where id=1;
commit;  
複製程式碼

當第二個事務執行了step 1,這個時候第三個事務的插入也執行完畢了,接著事務四開始執行,此時資料庫的資料如下:

id username 建立時間 (事務 ID) 刪除時間 (事務 ID)
1 tom 1 4
2 joey 1 undefined
3 james 1 undefined
4 yang 3 undefined

上面可以看出,當執行DELETE操作的時候,刪除時間(事務ID)列會存上當前事務的系統版本號;

然後step 2執行了,得到如下結果:

id username 建立時間 (事務 ID) 刪除時間 (事務 ID)
1 tom 1 4
2 joey 1 undefined
3 james 1 undefined

具體原因我就不說了(SELECT查詢的兩個條件);

UPDATE

InnoDB 執行 UPDATE,實際上是新插入的一行資料 ,並儲存其建立時間(事務ID)為當前事務的系統版本號,同時儲存當前事務系統版本號到需要UPDATE的行的刪除時間(事務ID)

情況三

第五個事務,系統版本號為5:

start transaction;
update mvcc set name='jack' where id = 3;
commit;
複製程式碼

當執行完step 1,第三個的插入和第四個事務的刪除都執行完畢並且提交,又有一個使用者執行了第五個事務的更新操作,這個時候,資料庫資料如下:

id username 建立時間 (事務 ID) 刪除時間 (事務 ID)
1 tom 1 4
2 joey 1 undefined
3 james 1 5
4 yang 3 undefined
3 jack 5 undefined

然後我們執行step 2得到如下資料:

id username 建立時間 (事務 ID) 刪除時間 (事務 ID)
1 tom 1 4
2 joey 1 undefined
3 james 1 5

以上幾種情況可以看出,不管咋樣,查出的資料都是和第一次查詢的資料一致,儘管其他事務做了各種修改操作,但是沒有影響到第二個事務中的查詢操作;

通過以上對MVCC的介紹,我想大家也明白了Repeatable read避免不可重複讀的方式;

參考文章:blog.csdn.net/whoamiyang/…

幻讀

幻讀:是指在一個事務內讀取到了別的事務插入的資料,導致前後讀取不一致 (幻讀是事務非獨立執行時發生的一種現象);

舉例說明:

例如事務A對一個表中符合條件的一些資料做了從a修改為b的操作,這時事務B又對這個表中插入了符合A修改條件的一行資料項,而這個資料項的數值還是為a並且提交給資料庫。而操作事務A的使用者如果再檢視剛剛修改的資料,會發現還有一行沒有修改,其實這行是從事務B中新增的,就好像產生幻覺一樣,這就是發生了幻讀。

解決:

但在MySQL實現的Repeatable read配合間隙鎖不會出現幻讀;

使用間隙鎖鎖住符合條件的部分,不允許插入符合條件的資料。

間隙鎖

間隙鎖:當我們用範圍條件檢索資料而不是相等條件檢索資料,並請求共享或排他鎖時,InnoDB會給符合範圍條件的已有資料記錄的索引項加鎖;對於鍵值在條件範圍內但並不存在的記錄,叫做“間隙(GAP)”。InnoDB也會對這個“間隙”加鎖,這種鎖機制就是所謂的間隙鎖(間隙鎖只會在Repeatable read隔離級別下使用)。

InnoDB使用間隙鎖的目的有兩個:

  • 為了防止幻讀
  • 滿足恢復和複製的需要
    • MySQL的恢復機制要求:在一個事務未提交前,其他併發事務不能插入滿足其鎖定條件的任何記錄,也就是不允許出現幻讀

總結

本文介紹了MySQL資料鎖以及事務的一些知識點,下面我們來總結一下;

事務的四大特性:

  • 原子性(Atomicity :事務作為一個整體被執行,包含在其中的對資料庫的操作要麼全部被執行,要麼都不執行。
  • 一致性(Consistency):事務應確保資料庫的狀態從一個一致狀態轉變為另一個一致狀態。一致狀態的含義是資料庫中的資料應滿足完整性約束。
  • 隔離性(Isolation):多個事務併發執行時,一個事務的執行不應影響其他事務的執行。
  • 永續性(Durability):一個事務一旦提交,他對資料庫的修改應該永久儲存在資料庫中。

對於事務的隔離級別也是很清楚的,分為四種:

  • Read uncommitted:未提交讀
    • 最低階別,會出現髒讀、不可重複讀、幻讀。
  • Read committed:已提交讀
    • 避免髒讀,會出現不可重複讀和幻讀。
  • Repeatable read:可重複讀
    • 避免髒讀和不可重複讀,會出現幻讀(在MySQL實現的Repeatable read配合gap鎖不會出現幻讀!)。
  • Serializable :序列化
    • 避免髒讀、不可重複讀、幻讀。

MVCC(Multi-Version Concurrency Control):多版本併發控制 ,一句話總結就是 同一份資料臨時保留多版本的一種方式,進而實現併發控制 (上面也簡單的演示了InnoDB MVCC的實現);

MVCC能夠實現讀寫不阻塞

快照有兩個級別

  • 語句級
    • 針對於Read committed隔離級別
  • 事務級別
    • 針對於Repeatable read隔離級別

Repeatable read避免不可重複讀是事務級別的快照!每次讀取的都是當前事務的版本,即使被修改了,也只會讀取當前事務版本的資料。

最後

本文簡單的說了一下事務一塊的東西,有問題的話還望大家指教,本人一定抱著虛心學習的態度。

大家共同學習,一起進步!

相關文章