久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

MySQL InnoDB之MVCC原理是什么

130次閱讀
沒有評論

共計 8728 個字符,預計需要花費 22 分鐘才能閱讀完成。

今天丸趣 TV 小編給大家分享一下 MySQL InnoDB 之 MVCC 原理是什么的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

MVCC 全稱 Multi-Version Concurrency Control,即多版本并發控制,主要是為了提高數據庫的并發性能。同一行數據平時發生讀寫請求時,會上鎖阻塞住。但 MVCC 用更好的方式去處理讀—寫請求,做到在發生讀—寫請求沖突時不用加鎖。這個讀是指的快照讀,而不是當前讀,當前讀是一種加鎖操作,是悲觀鎖。那它到底是怎么做到讀—寫不用加鎖的,快照讀和當前讀是指什么?我們后面都會學到。

MySQL 在 REPEATABLE READ 隔離級別下,是可以很大程度避免幻讀問題的發生的,MySQL 是怎么做到的?

版本鏈

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

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

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

為了說明這個問題,我們創建一個演示表:

CREATE TABLE `teacher` ( `number` int(11) NOT NULL,
 `name` varchar(100) DEFAULT NULL,
 `domain` varchar(100) DEFAULT NULL,
 PRIMARY KEY (`number`)) ENGINE=InnoDB DEFAULT CHARSET=utf8

然后向這個表里插入一條數據:

mysql  insert into teacher values(1,  J ,  Java Query OK, 1 row affected (0.01 sec)

現在里的數據就是這樣的:

mysql  select * from teacher;
+--------+------+--------+
| number | name | domain |
+--------+------+--------+
| 1 | J | Java |
+--------+------+--------+
1 row in set (0.00 sec)

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

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

Trx80Trx120begin

beginupdate teacher set name=‘S’where number=1;
update teacher set name=‘T’where number=1;
commit

update teacher set name=‘K’where number=1;
update teacher set name=‘F’where number=1;
commit

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

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

ReadView

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

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

對于使用 READ COMMITTED 和 REPEATABLE READ 隔離級別的事務來說,都必須保證讀到已經提交了的事務修改過的記錄,也就是說假如另一個事務已經修改了記錄但是尚未提交,是不能直接讀取最新版本的記錄的,核心問題就是:READ COMMITTED 和 REPEATABLE READ 隔離級別在不可重復讀和幻讀上的區別,這兩種隔離級別關鍵是需要判斷一下版本鏈中的哪個版本是當前事務可見的。

為此,InnoDB 提出了一個 ReadView 的概念,這個 ReadView 中主要包含 4 個比較重要的內容:

m_ids:表示在生成 ReadView 時當前系統中活躍的讀寫事務的事務 id 列表。

min_trx_id:表示在生成 ReadView 時當前系統中活躍的讀寫事務中最小的事務 id,也就是 m_ids 中的最小值。

max_trx_id:表示生成 ReadView 時系統中應該分配給下一個事務的 id 值。注意 max_trx_id 并不是 m_ids 中的最大值,事務 id 是遞增分配的。比方說現在有 id 為 1,2,3 這三個事務,之后 id 為 3 的事務提交了。那么一個新的讀事務在生成 ReadView 時,m_ids 就包括 1 和 2,min_trx_id 的值就是 1,max_trx_id 的值就是 4。

creator_trx_id:表示生成該 ReadView 的事務的事務 id。

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

如果被訪問版本的 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 之間 (min_trx_id = trx_id max_trx_id),那就需要判斷一下 trx_id 屬性值是不是在 m_ids 列表中,如果在,說明創建 ReadView 時生成該版本的事務還是活躍的,事務還沒提交,該版本不可以被訪問;如果不在,說明創建 ReadView 時生成該版本的事務已經被提交,該版本可以被訪問。

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

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

我們還是以表 teacher 為例,假設現在表 teacher 中只有一條由事務 id 為 60 的事務插入的一條記錄,接下來看一下 READ COMMITTED 和 REPEATABLE READ 所謂的生成 ReadView 的時機不同到底不同在哪里。

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

假設現在系統里有兩個事務 id 分別為 80、120 的事務在執行:

# Transaction 80
set session transaction isolation level read committed;
begin
update teacher set name= S  where number=1;
update teacher set name= T  where number=1;

此刻,表 teacher 中 number 為 1 的記錄得到的版本鏈表如下所示:

假設現在有一個使用 READ COMMITTED 隔離級別的事務開始執行:

set session transaction isolation level read committed;
#  使用 READ COMMITTED 隔離級別的事務
begin;
# SELECE1:Transaction 80、120 未提交
SELECT * FROM teacher WHERE number = 1; #  得到的列 name 的值為 J

這個 SELECE1 的執行過程如下:

在執行 SELECT 語句時會先生成一個 ReadView,ReadView 的 m_ids 列表的內容就是 [80, 120],min_trx_id 為 80,max_trx_id 為 121,creator_trx_id 為 0。

然后從版本鏈中挑選可見的記錄,最新版本的列 name 的內容是’T’,該版本的 trx_id 值為 80,在 m_ids 列表內,根據步驟 4 不符合可見性要求,根據 roll_pointer 跳到下一個版本。

下一個版本的列 name 的內容是’S’,該版本的 trx_id 值也為 80,也在 m_ids 列表內,根據步驟 4 也不符合要求,繼續跳到下一個版本。

下一個版本的列 name 的內容是’J’,該版本的 trx_id 值為 60,小于 ReadView 中的 min_trx_id 值,根據步驟 2 判斷這個版本是符合要求的。

之后,我們把事務 id 為 80 的事務提交一下,然后再到事務 id 為 120 的事務中更新一下表 teacher 中 number 為 1 的記錄:

set session transaction isolation level read committed;
# Transaction 120
begin
update teacher set name= K  where number=1;
update teacher set name= F  where number=1;

此刻,表 teacher 中 number 為 1 的記錄的版本鏈就長這樣:

然后再到剛才使用 READ COMMITTED 隔離級別的事務中繼續查找這個 number 為 1 的記錄,如下:

#  使用 READ COMMITTED 隔離級別的事務
begin;
# SELECE1:Transaction 80、120 未提交
SELECT * FROM teacher WHERE number = 1; #  得到的列 name 的值為 J 
# SELECE2:Transaction 80 提交、120 未提交
SELECT * FROM teacher WHERE number = 1; #  得到的列 name 的值為 T

這個 SELECE2 的執行過程如下:

在執行 SELECT 語句時會又會單獨生成一個 ReadView,該 ReadView 的 m_ids 列表的內容就是 [120](事務 id 為 80 的那個事務已經提交了,所以再次生成快照時就沒有它了),min_trx_id 為 120,max_trx_id 為 121,creator_trx_id 為 0。

然后從版本鏈中挑選可見的記錄,從圖中可以看出,最新版本的列 name 的內容是’F’,該版本的 trx_id 值為 120,在 m_ids 列表內,根據步驟 4 不符合可見性要求,根據 roll_pointer 跳到下一個版本。

下一個版本的列 name 的內容是’K’,該版本的 trx_id 值為 120,也在 m_ids 列表內,根據步驟 4 不符合可見性要求,根據 roll_pointer 跳到下一個版本。

下一個版本的列 name 的內容是’T’,該版本的 trx_id 值為 80,小于 ReadView 中的 min_trx_id 值 120,表明生成該版本的事務在當前事務生成 ReadView 前已經提交,所以這個版本是符合要求的,最后返回給用戶的版本就是這條列 name 為’‘T’的記錄。

以此類推,如果之后事務 id 為 120 的記錄也提交了,再次在使用 READCOMMITTED 隔離級別的事務中查詢表 teacher 中 number 值為 1 的記錄時,得到的結果就是’F’了,具體流程我們就不分析了。

總結一下就是:使用 READCOMMITTED 隔離級別的事務在每次查詢開始時都會生成一個獨立的 ReadView。

REPEATABLE READ —— 在第一次讀取數據時生成一個 ReadView

對于使用 REPEATABLE READ 隔離級別的事務來說,只會在第一次執行查詢語句時生成一個 ReadView,之后的查詢就不會重復生成了。我們還是用例子看一下是什么效果。

假設現在系統里有兩個事務 id 分別為 80、120 的事務在執行:

# Transaction 80
begin
update teacher set name= S  where number=1;
update teacher set name= T  where number=1;

此刻,表 teacher 中 number 為 1 的記錄得到的版本鏈表如下所示:

假設現在有一個使用 REPEATABLE READ 隔離級別的事務開始執行:

#  使用 REPEATABLE READ 隔離級別的事務
begin;
# SELECE1:Transaction 80、120 未提交
SELECT * FROM teacher WHERE number = 1; #  得到的列 name 的值為 J

這個 SELECE1 的執行過程如下(與 READ COMMITTED 的過程一致):

在執行 SELECT 語句時會先生成一個 ReadView,ReadView 的 m_ids 列表的內容就是 [80, 120],min_trx_id 為 80,max_trx_id 為 121,creator_trx_id 為 0。

然后從版本鏈中挑選可見的記錄,最新版本的列 name 的內容是’T’,該版本的 trx_id 值為 80,在 m_ids 列表內,根據步驟 4 不符合可見性要求,根據 roll_pointer 跳到下一個版本。

下一個版本的列 name 的內容是’S’,該版本的 trx_id 值也為 80,也在 m_ids 列表內,根據步驟 4 也不符合要求,繼續跳到下一個版本。

下一個版本的列 name 的內容是’J’,該版本的 trx_id 值為 60,小于 ReadView 中的 min_trx_id 值,根據步驟 2 判斷這個版本是符合要求的。

之后,我們把事務 id 為 80 的事務提交一下,然后再到事務 id 為 120 的事務中更新一下表 teacher 中 number 為 1 的記錄:

# Transaction 80
begin
update teacher set name= K  where number=1;
update teacher set name= F  where number=1;

此刻,表 teacher 中 number 為 1 的記錄的版本鏈就長這樣:

然后再到剛才使用 REPEATABLE READ 隔離級別的事務中繼續查找這個 number 為 1 的記錄,如下:

#  使用 REPEATABLE READ 隔離級別的事務
begin;
# SELECE1:Transaction 80、120 未提交
SELECT * FROM teacher WHERE number = 1; #  得到的列 name 的值為 J
# SELECE2:Transaction 80 提交、120 未提交
SELECT * FROM teacher WHERE number = 1; #  得到的列 name 的值為 J 

這個 SELECE2 的執行過程如下:

因為當前事務的隔離級別為 REPEATABLE READ,而之前在執行 SELECE1 時已經生成過 ReadView 了,所以此時直接復用之前的 ReadView,之前的 ReadView 的 m_ids 列表的內容就是 [80, 120],min_trx_id 為 80,max_trx_id 為 121,creator_trx_id 為 0。

然后從版本鏈中挑選可見的記錄,從圖中可以看出,最新版本的列 name 的內容是’F’,該版本的 trx_id 值為 120,在 m_ids 列表內,根據步驟 4 不符合可見性要求,根據 roll_pointer 跳到下一個版本。

下一個版本的列 name 的內容是’K’,該版本的 trx_id 值為 120,也在 m_ids 列表內,根據步驟 4 不符合可見性要求,根據 roll_pointer 跳到下一個版本。

下一個版本的列 name 的內容是’T’,該版本的 trx_id 值為 80,也在 m_ids 列表內,根據步驟 4 不符合可見性要求,根據 roll_pointer 跳到下一個版本。

下一個版本的列 name 的內容是’S’,該版本的 trx_id 值為 80,也在 m_ids 列表內,根據步驟 4 不符合可見性要求,根據 roll_pointer 跳到下一個版本。

下一個版本的列 name 的內容是’J’,該版本的 trx_id 值為 60,小于 ReadView 中的 min_trx_id 值 80,表明生成該版本的事務在當前事務生成 ReadView 前已經提交,所以這個版本是符合要求的,最后返回給用戶的版本就是這條列 name 為’‘J’的記錄。

也就是說兩次 SELECT 查詢得到的結果是重復的,記錄的列值都是’’‘J’’’,這就是可重復讀的含義。

如果我們之后再把事務 id 為 120 的記錄提交了,然后再到剛才使用 REPEATABLE READ 隔離級別的事務中繼續查找這個 number 為 1 的記錄,得到的結果還是’J’,具體執行過程大家可以自己分析一下。

MVCC 下的幻讀現象和幻讀解決

前面我們已經知道了,REPEATABLE READ 隔離級別下 MVCC 可以解決不可重復讀問題,那么幻讀呢?MVCC 是怎么解決的?幻讀是一個事務按照某個相同條件多次讀取記錄時,后讀取時讀到了之前沒有讀到的記錄,而這個記錄來自另一個事務添加的新記錄。

我們可以想想,在 REPEATABLE READ 隔離級別下的事務 T1 先根據某個搜索條件讀取到多條記錄,然后事務 T2 插入一條符合相應搜索條件的記錄并提交,然后事務 T1 再根據相同搜索條件執行查詢。結果會是什么?按照 ReadView 中的比較規則:

不管事務 T2 比事務 T1 是否先開啟,事務 T1 都是看不到 T2 的提交的。請自行按照上面介紹的版本鏈、ReadView 以及判斷可見性的規則來分析一下。

但是,在 REPEATABLE READ 隔離級別下 InnoDB 中的 MVCC 可以很大程度地避免幻讀現象,而不是完全禁止幻讀。怎么回事呢?我們來看下面的情況:

T1T2begin;
select * from teacher where number=30; 無數據 begin;
insert into teacher values(30,‘X’,‘Java’);
commit;update teacher set domain=‘MQ’where number=30;
select * from teacher where number = 30; 有數據

嗯,怎么回事?事務 T1 很明顯出現了幻讀現象。在 REPEATABLE READ 隔離級別下,T1 第一次執行普通的 SELECT 語句時生成了一個 ReadView,之后 T2 向 teacher 表中新插入一條記錄并提交。ReadView 并不能阻止 T1 執行 UPDATE 或者 DELETE 語句來改動這個新插入的記錄(由于 T2 已經提交,因此改動該記錄并不會造成阻塞 ),但是這樣一來,這條新記錄的 trx_id 隱藏列的值就變成了 T1 的事務 id。之后 T1 再使用普通的 SELECT 語句去查詢這條記錄時就可以看到這條記錄了,也就可以把這條記錄返回給客戶端。因為這個特殊現象的存在,我們也可以認為 MVCC 并不能完全禁止幻讀。

MVCC 小結

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

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

我們之前說執行 DELETE 語句或者更新主鍵的 UPDATE 語句并不會立即把對應的記錄完全從頁面中刪除,而是執行一個所謂的 delete mark 操作,相當于只是對記錄打上了一個刪除標志位,這主要就是為 MVCC 服務的。另外,所謂的 MVCC 只是在我們進行普通的 SEELCT 查詢時才生效,截止到目前我們所見的所有 SELECT 語句都算是普通的查詢,至于什么是個不普通的查詢,后面就會講到。

以上就是“MySQL InnoDB 之 MVCC 原理是什么”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,丸趣 TV 小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注丸趣 TV 行業資訊頻道。

正文完
 
丸趣
版權聲明:本站原創文章,由 丸趣 2023-07-13發表,共計8728字。
轉載說明:除特殊說明外本站除技術相關以外文章皆由網絡搜集發布,轉載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 桂平市| 曲周县| 永顺县| 寿宁县| 和林格尔县| 谷城县| 南丰县| 镇赉县| 霞浦县| 若羌县| 甘洛县| 乐昌市| 新闻| 陆丰市| 五原县| 墨脱县| 乐亭县| 万州区| 张家川| 浮山县| 洞口县| 民勤县| 安新县| 铁岭市| 安陆市| 台州市| 会同县| 遂溪县| 德州市| 新余市| 濮阳市| 武义县| 安泽县| 九寨沟县| 治县。| 高密市| 石泉县| 项城市| 昌都县| 济宁市| 龙陵县|