一篇文章帶你掌握mysql的一致性視圖(MVCC)

提到事務,你確定會想到ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔離性、持久性),咱們就來講說其中I,也就是「隔離性」。html

當數據庫上有多個事務同時執行的時候,就可能出現髒讀(dirty read)、不可重複讀(non-repeatable read)、幻讀(phantom read)的問題,因此下面咱們來講說隔離級別。mysql

SQL標準的事務隔離級別包括:讀未提交(read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)、串行化(serializable)。sql

  • 讀未提交是指,一個事務還沒提交時,它作的變動就能被別的事務看到。
  • 讀提交指,一個事務提交以後,它作的變動纔會被其餘事務看到。
  • 可重複讀指,一個事務執行過程當中看到的數據,老是跟這個事務在啓動時看到的數據時一致的。固然可重複讀隔離級別下,未提交變動對其餘事務也是不可見的。
  • 串行化,顧名思義是對於同一行記錄,「寫」會加「寫鎖」,「讀」會加「讀鎖」。當出現讀寫鎖衝突的時候,後訪問的事務必須等前一個事務執行完成,才能繼續執行。

MySQL中支持的四種隔離級別

MySQL雖然支持4種隔離級別,但與SQL標準中所規定的各級隔離級別容許發生的問題卻有些出入,MySQL在REPEATABLE READ隔離級別下,是能夠禁止幻讀問題的發生的。數據庫

咱們能夠經過: SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level; 來設置隔離級別。bash

其中的level可選值有4個:spa

level: {
     REPEATABLE READ
   | READ COMMITTED
   | READ UNCOMMITTED
   | SERIALIZABLE
}
複製代碼

MVCC原理

對於使用InnoDB存儲引擎的表來講,它的聚簇索引記錄中都包含必要的隱藏列:code

  • trx_id:每次一個事務對某條聚簇索引記錄進行改動時,都會把該事務的事務id賦值給trx_id隱藏列。

ReadView

ReadView所解決的問題是使用READ COMMITTED和REPEATABLE READ隔離級別的事務中,不能讀到未提交的記錄,這須要判斷一下版本鏈中的哪一個版本是當前事務可見的。cdn

ReadView中主要包含4個比較重要的內容:htm

  • m_ids:表示在生成ReadView時當前系統中活躍的讀寫事務的事務id列表。
  • min_trx_id:表示在生成ReadView時當前系統中活躍的讀寫事務中最小的事務id,也就是m_ids中的最小值。
  • max_trx_id:表示生成ReadView時系統中應該分配給下一個事務的id值。
  • creator_trx_id:表示生成該ReadView的事務的事務id。

ReadView是如何工做的?

有了這些信息,這樣在訪問某條記錄時,只須要按照下邊的步驟判斷記錄的某個版本是否可見:blog

  • 若是被訪問版本的trx_id屬性值與ReadView中的creator_trx_id值相同,意味着當前事務在訪問它本身修改過的記錄,因此該版本能夠被當前事務訪問。
  • 若是被訪問版本的trx_id屬性值小於ReadView中的min_trx_id值,代表生成該版本的事務在當前事務生成ReadView前已經提交,因此該版本能夠被當前事務訪問。
  • 若是被訪問版本的trx_id屬性值大於ReadView中的max_trx_id值,代表生成該版本的事務在當前事務生成ReadView後纔開啓,因此該版本不能夠被當前事務訪問。
  • 若是被訪問版本的trx_id屬性值在ReadView的min_trx_id和max_trx_id之間,那就須要判斷一下trx_id屬性值是否是在m_ids列表中,若是在,說明建立ReadView時生成該版本的事務仍是活躍的,該版本不能夠被訪問;若是不在,說明建立ReadView時生成該版本的事務已經被提交,該版本能夠被訪問。

若是某個版本的數據對當前事務不可見的話,那就順着版本鏈找到下一個版本的數據,繼續按照上邊的步驟判斷可見性,依此類推,直到版本鏈中的最後一個版本。若是最後一個版本也不可見的話,那麼就意味着該條記錄對該事務徹底不可見,查詢結果就不包含該記錄。

在MySQL中,READ COMMITTED和REPEATABLE READ隔離級別的的一個很是大的區別就是它們生成ReadView的時機不一樣。

咱們這裏使用一個示例來解釋:

mysql> CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `k` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t(id, k) values(1,1) ;
複製代碼
事務A 事務B
begin
begin
update t set k= k+1 where id=1;
commit;
update t set k = k+1 where id=1;
select k from t where id =1;
commit;

在這個例子中,咱們作以下假設:

  1. 事務A、B的版本號分別是100、200,且當前系統裏只有這3個事務;
  2. 三個事務開始前,(1,1)這一行數據的row trx_id是90。

READ COMMITTED —— 每次讀取數據前都生成一個ReadView

繼續上面的例子,假設如今有一個使用READ COMMITTED隔離級別的事務開始執行:

# 使用READ COMMITTED隔離級別的事務
BEGIN;

# SELECT1:Transaction 100、200未提交
select k from t where id=1 ; # 獲得值爲1
複製代碼

這個SELECT1的執行過程以下:

  • 在執行SELECT語句時會先生成一個ReadView,ReadView的m_ids列表的內容就是[100, 200],min_trx_id爲100,max_trx_id爲201,creator_trx_id爲0。
  • 而後從版本鏈中挑選可見的記錄,最新的版本trx_id值爲200,在m_ids列表內,因此不符合可見性要求
  • 下一個版本的trx_id值也爲100,也在m_ids列表內,因此也不符合要求,繼續跳到下一個版本。
  • 下一個版本的trx_id值爲90,小於ReadView中的min_trx_id值100,因此這個版本是符合要求的。

以後,咱們把事務B的事務提交一下,而後再到剛纔使用READ COMMITTED隔離級別的事務中繼續查找,以下:

# 使用READ COMMITTED隔離級別的事務
BEGIN;

# SELECT1:Transaction 100、200均未提交
SELECT * FROM hero WHERE number = 1; # 獲得值爲1

# SELECT2:Transaction 200提交,Transaction 100未提交
SELECT * FROM hero WHERE number = 1; # 獲得值爲2
複製代碼

這個SELECT2的執行過程以下:

  • 在執行SELECT語句時會又會單獨生成一個ReadView,該ReadView的m_ids列表的內容就是[100](事務id爲200的那個事務已經提交了,因此再次生成快照時就沒有它了),min_trx_id爲100,max_trx_id爲201,creator_trx_id爲0。
  • 而後從版本鏈中挑選可見的記錄,從圖中能夠看出,最新版本trx_id值爲100,在m_ids列表內,因此不符合可見性要求
  • 下一個版本的trx_id值爲200,小於max_trx_id,而且不在m_ids列表中,因此可見,返回的值爲2

REPEATABLE READ —— 在第一次讀取數據時生成一個ReadView 假設如今有一個使用REPEATABLE READ隔離級別的事務開始執行:

# 使用REPEATABLE READ隔離級別的事務
BEGIN;

# SELECT1:Transaction 100、200未提交
SELECT * FROM hero WHERE number = 1; # 獲得值爲1
複製代碼

這個SELECT1的執行過程以下:

  • 在執行SELECT語句時會先生成一個ReadView,ReadView的m_ids列表的內容就是[100, 200],min_trx_id爲100,max_trx_id爲201,creator_trx_id爲0。
  • 而後從版本鏈中挑選可見的記錄,該版本的trx_id值爲100,在m_ids列表內,因此不符合可見性要求
  • 下一個版本該版本的trx_id值爲200,也在m_ids列表內,因此也不符合要求,繼續跳到下一個版本。
  • 下一個版本的trx_id值爲90,小於ReadView中的min_trx_id值100,因此這個版本是符合要求的。

以後,咱們把事務B的事務提交一下 而後再到剛纔使用REPEATABLE READ隔離級別的事務中繼續查找:

# 使用REPEATABLE READ隔離級別的事務
BEGIN;

# SELECT1:Transaction 100、200均未提交
SELECT * FROM hero WHERE number = 1; # 獲得值爲1

# SELECT2:Transaction 200提交,Transaction 100未提交
SELECT * FROM hero WHERE number = 1; # 獲得值爲1
複製代碼

這個SELECT2的執行過程以下:

  • 由於當前事務的隔離級別爲REPEATABLE READ,而以前在執行SELECT1時已經生成過ReadView了,因此此時直接複用以前的ReadView,以前的ReadView的m_ids列表的內容就是[100, 200],min_trx_id爲100,max_trx_id爲201,creator_trx_id爲0。
  • 而後從版本鏈中挑選可見的記錄,該版本的trx_id值爲100,在m_ids列表內,因此不符合可見性要求
  • 下一個版本該版本的trx_id值爲200,也在m_ids列表內,因此也不符合要求,繼續跳到下一個版本。
  • 下一個版本的trx_id值爲90,小於ReadView中的min_trx_id值100,因此這個版本是符合要求的。

原文連接 一篇文章帶你掌握mysql的一致性視圖(MVCC)

相關文章
相關標籤/搜索