事務與MVCC

前言

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

數據庫事務有不一樣的隔離級別,不一樣的隔離級別對鎖的使用是不一樣的,鎖的應用最終致使不一樣事務的隔離級別;在上一篇文章中咱們說到了數據庫鎖的一部分知識,知道了InnoDB是支持行鎖的,可是走行鎖是基於索引的;面試

這裏咱們會說一下和鎖緊密相關的事務;sql

但願本文對你們有所幫助;數據庫

引入

本文參考文章:數據庫的兩大神器併發

事務和MVCC

數據庫事務有不一樣的隔離級別,不一樣的隔離級別對鎖的使用是不一樣的,鎖的應用最終致使不一樣事務的隔離級別mvc

關於事務,你們也是比較熟悉的,在這裏咱們再來嘮叨一下:post

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

特性

事務具備四個特性:原子性、一致性、隔離性、持久性。這四個屬性一般被稱爲ACID屬性。spa

  • 原子性(Atomicity :事務做爲一個總體被執行,包含在其中的對數據庫的操做要麼所有被執行,要麼都不執行。
  • 一致性(Consistency:事務應確保數據庫的狀態從一個一致狀態轉變爲另外一個一致狀態。一致狀態的含義是數據庫中的數據應知足完整性約束。
  • 隔離性(Isolation:多個事務併發執行時,一個事務的執行不該影響其餘事務的執行。
  • 持久性(Durability:一個事務一旦提交,他對數據庫的修改應該永久保存在數據庫中。

對於以上的四個特性,咱們來拿經典的轉帳的例子來講明;.net

有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避免不可重複讀是事務級別的快照!每次讀取的都是當前事務的版本,即便被修改了,也只會讀取當前事務版本的數據。

最後

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

你們共同窗習,一塊兒進步!

相關文章
相關標籤/搜索