mysql數(shù)據(jù)mvcc版本控制原理

事務(wù)并發(fā)執(zhí)行遇到的問(wèn)題

  1. 臟讀(未提交讀)
  2. 不可重復(fù)讀(已提交讀)
  3. 幻讀(讀出新紀(jì)錄)

事務(wù)隔離級(jí)別

隔離級(jí)別 臟讀 不可重復(fù)讀 幻讀
未提交讀 可能 可能 可能
已提交讀 —— 可能 可能
可重復(fù)讀 —— —— ——
可串行化 —— —— ——

mysql在可重復(fù)讀的級(jí)別下,極大的程度上避免了幻讀。

版本鏈

對(duì)于InnoDB引擎來(lái)說(shuō),每個(gè)主鍵索引記錄中包含了兩個(gè)隱藏列

隱藏列
trx_id 記錄當(dāng)前正在操作此條記錄的事務(wù)id
roll_pointer 修改時(shí),記錄舊的版本到undo日志中, 當(dāng)前列就是指向舊版本的指針,以便找到修改前的信息

示例

  1. 創(chuàng)建一張表
CREATE TABLE teacher ( number INT,
name VARCHAR(100), domain varchar(100), PRIMARY KEY (number)
) Engine=InnoDB CHARSET=utf8;
  1. 新增一條記錄,并且當(dāng)前插入的事物id為60
INSERT INTO teacher VALUES(1, 'Jack', '源碼系列');

此刻生成一條記錄,如下圖所示

insert
  1. 之后有兩個(gè)80、120事務(wù)更新
trx_id(80) trx_id(120)
BEGIN
BEGIN
UPDATE teacher SET name = 'Mark' WHERE number = 1;
UPDATE teacher SET name = 'James' WHERE number = 1;
COMMIT
UPDATE teacher SET name = 'King' WHERE number = 1;
UPDATE teacher SET name = '大飛' WHERE number = 1;
COMMIT

每次記錄修改都有一條undo日志,每條undo日志都會(huì)一個(gè)roll_pointer屬性??梢詫⑦@些undo日志串成鏈表,我們把這種鏈表稱為版本鏈。

msyql版本鏈

對(duì)該記錄每次更新后,都會(huì)將舊值放到一條 undo 日志中,就算是該記錄的 一個(gè)舊版本,隨著更新次數(shù)的增多,所有的版本都會(huì)被 roll_pointer 屬性連接成 一個(gè)鏈表,我們把這個(gè)鏈表稱之為版本鏈

ReadView

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

id 說(shuō)明
m_ids 生成 ReadView 時(shí)當(dāng)前系統(tǒng)中活躍的讀寫事務(wù)的事務(wù) id 列表
min_trx_id 生成 ReadView 時(shí)當(dāng)前系統(tǒng)中活躍的讀寫事務(wù)中最小的事務(wù) id,也就是 m_ids 中的最小值。
max_trx_id 生成 ReadView 時(shí)系統(tǒng)中應(yīng)該分配給下一個(gè)事務(wù)的 id 值
creator_trx_id 生成該 ReadView 的事務(wù)的事務(wù) id

訪問(wèn)某條記錄時(shí),使用ReadView,遍歷版本鏈,通過(guò)下面規(guī)則判斷某個(gè)節(jié)點(diǎn)數(shù)據(jù)是否可以訪問(wèn)。

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

在 MySQL 中,已提交讀(RC)和可重復(fù)讀(RR)隔離級(jí)別的的一個(gè)非 常大的區(qū)別就是它們生成 ReadView 的時(shí)機(jī)不同。

  1. 已提交讀(RC)在每次讀取數(shù)據(jù)前都生成一個(gè) ReadView。
  2. 可重復(fù)讀(RR)在第一次讀取數(shù)據(jù)時(shí)生成一個(gè) ReadView。

mysql默認(rèn)的事務(wù)隔離級(jí)別是可重復(fù)讀(RC)

mysql可重復(fù)讀(RR)基本解決了幻讀問(wèn)題,那什么情況下會(huì)出現(xiàn)幻讀問(wèn)題呢。

  1. 假設(shè)有T1,T2,兩個(gè)事務(wù),T1先開啟事務(wù)。
  2. T2插入一條記錄,并提交事務(wù)
  3. T1查詢T2插入的記錄,很明顯查詢不到。
  4. 如果此刻T1更新了T2插入的那條記錄
  5. 此刻T1再去查詢那條記錄,這時(shí)是可以查到的,這就是幻讀

這種情況在實(shí)際生產(chǎn)并不多。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容