欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

MySQL多版本并發(fā)控制MVCC底層原理解析

 更新時間:2021年12月17日 17:04:50   作者:你攜秋月攬星河丶  
本文詳細講解了MySQL多版本并發(fā)控制MVCC底層原理,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

1 事務并發(fā)中遇到的問題

1.1 臟讀

當一個事務讀取到了另外一個事務修改但未提交的數(shù)據(jù),被稱為臟讀。

1.2 不可重復讀

當事務內(nèi)相同的記錄被檢索兩次,且兩次得到的結果不同時,此現(xiàn)象稱為不可重復讀。

1.3 幻讀

當一個事務同樣的查詢條件查詢兩次(多次),查出的條數(shù)不一致稱為幻讀。

2 隔離級別

我們上邊介紹了幾種并發(fā)事務執(zhí)行過程中可能遇到的一些問題,這些問題也有輕重緩急之分,我們給這些問題按照嚴重性來排一下序:

臟讀 > 不可重復讀 > 幻讀

SQL 標準中規(guī)定,針對不同的隔離級別,并發(fā)事務可以發(fā)生不同嚴重程度的

問題,具體情況如下:

  • READ UNCOMMITTED:未提交讀。
  • READ COMMITTED:已提交讀。
  • REPEATABLE READ:可重復讀。
  • SERIALIZABLE:可串行化

SQL 標準中規(guī)定,針對不同的隔離級別,并發(fā)事務可以發(fā)生不同嚴重程度的問題,具體情況如下:

  • READ UNCOMMITTED 隔離級別下,可能發(fā)生臟讀、不可重復讀和幻讀問題。
  • READ COMMITTED 隔離級別下,可能發(fā)生不可重復讀和幻讀問題,但是不可以發(fā)生臟讀問題。
  • REPEATABLE READ 隔離級別下,可能發(fā)生幻讀問題,但是不可以發(fā)生臟讀和不可重復讀的問題。
  • SERIALIZABLE 隔離級別下,各種問題都不可以發(fā)生。

3 版本鏈

我們知道,對于使用 InnoDB 存儲引擎的表來說,它的聚簇索引記錄中都包含兩個必要的隱藏列(row_id 并不是必要的,我們創(chuàng)建的表中有主鍵或者非 NULL的 UNIQUE 鍵時都不會包含 row_id 列):

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

roll_pointer: 每次對某條聚簇索引記錄進行改動時,都會把舊的版本寫入到undo 日志中,然后這個隱藏列就相當于一個指針,可以通過它來找到該記錄修改前的信息。

假設插入該記錄的事務 id 為 80的記錄,那么此刻該條記錄的示意圖如下所示:

假設之后兩個事務 id 分別為 100、200 的事務對這條記錄進行 UPDATE 操作,操作流程如下:

Trx 100:

UPDATE t_people SET name = '關羽' WHERE number = 1;
UPDATE t_people SET name = '張飛' WHERE number = 1;

Trx 200:

UPDATE t_people SET name = '趙云' WHERE number = 1;
UPDATE t_people SET name = '諸葛亮' WHERE number = 1;

每次對記錄進行改動,都會記錄一條 undo 日志,每條 undo 日志也都有一個 roll_pointer 屬性(INSERT 操作對應的 undo 日志沒有該屬性,因為該記錄并沒有更早的版本),可以將這些 undo 日志都連起來,串成一個鏈表,所以現(xiàn)在的情況就像下圖一樣:

對該記錄每次更新后,都會將舊值放到一條 undo 日志中,就算是該記錄的一個舊版本,隨著更新次數(shù)的增多,所有的版本都會被 roll_pointer 屬性連接成一個鏈表,我們把這個鏈表稱之為版本鏈,版本鏈的頭節(jié)點就是當前記錄最新的值。另外,每個版本中還包含生成該版本時對應的事務 id。于是可以利用這個記錄的版本鏈來控制并發(fā)事務訪問相同記錄的行為,那么這種機制就被稱之為多版本并發(fā)控制(Mulit-Version Concurrency Control MVCC)。

4 ReadView

4.1 ReadView 定義

InnoDB 提出了一個 ReadView 的概念,這個 ReadView 中主要包含 4個比較重要的內(nèi)容:

  • (1) m_ids:表示在生成 ReadView 時當前系統(tǒng)中 活躍 的讀寫事務的事務 id 列表。
  • (2) min_trx_id: 表示在生成 ReadView 時當前系統(tǒng)中活躍的讀寫事務中最小的事務 id,也就是 m_ids 中的最小值。
  • (3) max_trx_id:表示生成 ReadView 時系統(tǒng)中應該分配給下一個事務的 id 值。max_trx_id 并不是 m_ids 中的最大值,事務 id 是遞增分配的。比方說現(xiàn)在有 id 為 1,2,3 這三個事務,之后 id 為 3 的事務提交了。那么一個新的讀事務在生成 ReadView 時,m_ids 就包括 1 和 2,min_trx_id 的值就是 1,max_trx_id的值就是 4。
  • (4) creator_trx_id:表示生成該 ReadView 的事務的事務 id。

4.2 訪問控制

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

  • (1) 如果被訪問版本的 trx_id 屬性值與 ReadView 中的 creator_trx_id 值相同,意味著當前事務在訪問它自己修改過的記錄,所以該版本可以被當前事務訪問。
  • (2) 如果被訪問版本的 trx_id 屬性值小于 ReadView 中的 min_trx_id 值,表明生成該版本的事務在當前事務生成 ReadView 前已經(jīng)提交,所以該版本可以被當前事務訪問。
  • (3) 如果被訪問版本的 trx_id 屬性值大于或等于 ReadView 中的 max_trx_id值,表明生成該版本的事務在當前事務生成 ReadView 后才開啟,所以該版本不可以被當前事務訪問。
  • (4) 如果被訪問版本的 trx_id 屬性值在 ReadView 的 min_trx_id 和 max_trx_id之間(min_trx_id < trx_id < max_trx_id),那就需要判斷一下 trx_id 屬性值是不是在m_ids 列表中,如果在,說明創(chuàng)建 ReadView 時生成該版本的事務還是活躍的,該版本不可以被訪問;如果不在,說明創(chuàng)建 ReadView 時生成該版本的事務已經(jīng)被提交,該版本可以被訪問。
  • (5) 如果某個版本的數(shù)據(jù)對當前事務不可見的話,那就順著版本鏈找到下一個版本的數(shù)據(jù),繼續(xù)按照上邊的步驟判斷可見性,依此類推,直到版本鏈中的最后一個版本。如果最后一個版本也不可見的話,那么就意味著該條記錄對該事務完全不可見,查詢結果就不包含該記錄。

4.3 再談隔離

對于使用 READ UNCOMMITTED 隔離級別的事務來說,由于可以讀到未提交事務修改過的記錄,所以直接讀取記錄的最新版本就好了。

對于使用 SERIALIZABLE 隔離級別的事務來說,InnoDB 使用加鎖的方式來訪問記錄。

在 MySQL 中,READ COMMITTED 和 REPEATABLE READ 隔離級別的的一個非常大的區(qū)別就是它們生成 ReadView 的時機不同。

4.3.1 READ COMMITTED(讀已提交)

讀已提交,每次讀取數(shù)據(jù)前都生成一個 ReadView。

假設現(xiàn)在有一個使用 READ COMMITTED 隔離級別的事務開始執(zhí)行:

詳解查詢:

#使用 READ COMMITTED 隔離級別的事務
#Transaction 100、200未提交,得到的列 name 的值為 劉備
SELECT name FROM t_people WHERE number = 1;  

這個 SELECET 的執(zhí)行過程如下:

  • (1) 在執(zhí)行 SELECT 語句時會先生成一個 ReadView,ReadView 的 m_ids 列表的內(nèi)容就是[100, 200],min_trx_id 為 100,max_trx_id 為 201,creator_trx_id 為 0。
  • (2) 然后從版本鏈中挑選可見的記錄,從圖中可以看出,最新版本的列 name 的內(nèi)容是諸葛亮,該版本的 trx_id 值為 200,在 m_ids 列表內(nèi),所以不符合可見性要求。(如果被訪問版本的 trx_id 屬性值在 ReadView 的 min_trx_id 和 max_trx_id之間,就需要判斷一下 trx_id 屬性值是不是在m_ids 列表中,如果在,說明創(chuàng)建 ReadView 時生成該版本的事務還是活躍的,該版本不可以被訪問;如果不在,說明創(chuàng)建 ReadView 時生成該版本的事務已經(jīng)被提交,該版本可以被訪問 ),根據(jù) roll_pointer 跳到下一個版本。
  • (3) 諸葛亮下一個版本的列name的內(nèi)容是趙云,該版本的trx_id值也為200,也在m_ids列表內(nèi),所以也不符合要求,繼續(xù)跳到下一個版本。
  • (4) 趙云下一個版本的列name的內(nèi)容是張飛,該版本的trx_id值也為100,也在m_ids列表內(nèi),所以也不符合要求,繼續(xù)跳到下一個版本。
  • (5) 張飛下一個版本的列name的內(nèi)容是關羽,該版本的trx_id值也為100,也在m_ids列表內(nèi),所以也不符合要求,繼續(xù)跳到下一個版本。
  • (6) 關羽下一個版本是劉備,該版本的trx_id值為80,小于ReadView 中的 min_trx_id 值,所以這個版本是符合要求的。

不可重復讀: 100事務、200事務開啟讀取到name都為劉備。當100事務提交時,由于是讀已提交事務隔離級別,每次讀取都會創(chuàng)建ReadView,200事務讀取時,創(chuàng)建生成的ReadView m_ids 為 [200],這時根據(jù)讀取規(guī)則讀取到的name就為張飛。

# 使用 READ COMMITTED 隔離級別的事務
BEGIN;
# SELECE1:Transaction 100、200 均未提交,得到name值為劉備
SELECT name  FROM t_people WHERE number = 1; 

# SELECE2:Transaction 100 提交,Transaction 200 未提交
#Transaction 200 事務查詢,得到name值為張飛,發(fā)生不可重復讀。
SELECT name  FROM teacher WHERE number = 1; 

4.3.2 REPEATABLE READ(可重讀)

可重讀,在第一次讀取數(shù)據(jù)時生成一個 ReadView。

解決不可重復讀: 100事務、200事務開啟,創(chuàng)建ReadView,m_ids 為[100,200],讀取到name都為劉備。當100事務提交時,由于是可重讀事務隔離級別,只創(chuàng)建一次ReadView,m_ids 仍然是[100,200],這時根據(jù)讀取規(guī)則讀取到的name仍然是劉備。

5 幻讀

當一個事務同樣的查詢條件查詢兩次(多次),查出的條數(shù)不一致稱為幻讀。

在 REPEATABLE READ 隔離級別下的事務 T1 先根據(jù)某個搜索條件讀取到多條記錄,然后事務 T2 插入一條符合相應搜索條件的記錄并提交,然后事務 T1 再根據(jù)相同搜索條件執(zhí)行查詢。結果會是什么?按照 ReadView 中的比較規(guī)則,不管事務 T2 比事務 T1 是否先開啟,事務 T1 都是看不到 T2 的提交的。但是,在 REPEATABLE READ 隔離級別下 InnoDB 中的 MVCC 可以很大程度地避免幻讀現(xiàn)象,而不是完全禁止幻讀。

#SELECT:快照讀。update:當前讀。
REPEATABLE READ 可以解決快照讀幻讀問題。解決不了當前讀幻讀的問題。

案例:

1 執(zhí)行begin,執(zhí)行select *。

2 開啟另一個窗口 執(zhí)行insert、select、commit。

3 回到原窗口執(zhí)行查詢

4 執(zhí)行 update 、提交

5 查找

6 總結

從上邊的描述中我們可以看出來,所謂的 MVCC(Multi-Version ConcurrencyControl ,多版本并發(fā)控制)指的就是在使用 READ COMMITTD、REPEATABLE READ這兩種隔離級別的事務在執(zhí)行普通的 SELECT 操作時訪問記錄的版本鏈的過程,這樣子可以使不同事務的讀-寫、寫-讀操作并發(fā)執(zhí)行,從而提升系統(tǒng)性能。

READ COMMITTD、REPEATABLE READ 這兩個隔離級別的一個很大不同就是,生成 ReadView 的時機不同,READ COMMITTD 在每一次進行普通 SELECT 操作前都會生成一個 ReadView,而 REPEATABLE READ 只在第一次進行普通 SELECT 操作前生成一個 ReadView,之后的查詢操作都重復使用這個 ReadView 就好了,從而基本上可以避免幻讀現(xiàn)象。

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

最新評論