前言
本文整理一下,MySQL在RC、RR級別下的InnoDB的非阻塞讀如何實現
什麼是MVCC
MVCC
(Multi-Version Concurrency Control ,多版本併發控制)指的就是在使用READ COMMITTD
、REPEATABLE READ
這兩種隔離級別的事務在執行普通的SELECT
操作時訪問記錄的版本鏈的過程,這樣子可以使不同事務的讀-寫
、寫-讀
操作併發執行,從而提升系統性能。
事務id什麼時候創建的?
如果某個事務執行過程中對某個表執行了增、刪、改操作,那麼InnoDB存儲引擎就會給它分配一個獨一無二的事務id,分配方式如下:
- 對於只讀事務來說,只有在它第一次對某個用戶創建的臨時表執行增、刪、改操作時纔會爲這個事務分配一個事務id,否則的話是不分配事務id的。
- 對於讀寫事務來說,只有在它第一次對某個表(包括用戶創建的臨時表)執行增、刪、改操作時纔會爲這個事務分配一個事務id,否則的話也是不分配事務id的。
事務id是怎麼生成的?
這個事務id本質上就是一個數字,具體策略如下:
-
服務器會在內存中維護一個全局變量,每當需要爲某個事務分配id時,將該值分給該事務,變量值自增+1
-
每當這個變量的值爲
256
的倍數時,就會將該變量的值刷新到系統表空間的頁號爲5
的頁面中一個稱之爲Max Trx ID
的屬性處,這個屬性佔用8
個字節的存儲空間。 -
當系統下一次重新啓動時,會將上邊提到的
Max Trx ID
屬性加載到內存中,將該值加上256之後賦值給我們前邊提到的全局變量(因爲在上次關機時該全局變量的值可能大於Max Trx ID
屬性值)。
這樣就可以保證整個系統中分配的事務id
值是一個遞增的數字。先被分配id
的事務得到的是較小的事務id
,後被分配id
的事務得到的是較大的事務id
。
RC、RR隔離級別下,生成ReadView的時機
READ UNCOMMITTED:不會生成ReadView,直接讀取記錄的最新版本即可。
READ COMMITTD:在每一次進行普通SELECT操作前都會生成一個ReadView。
REPEATABLE READ:只有第一次進行普通SELECT操作前生成一個ReadView,之後複用同一ReadView。
原理
對於使用InnoDB
存儲引擎的表來說,它的聚簇索引記錄中都包含兩個必要的隱藏列,比如上方的 t 表
隱藏列 | 說明 |
---|---|
trx_id | 每次一個事務對某條聚簇索引記錄進行改動時,都會把該事務的事務id 賦值給trx_id 隱藏列 |
roll_pointer | 每次對某條聚簇索引記錄進行改動時,都會把舊的版本寫入到undo日誌 中,然後這個隱藏列就相當於一個指針,可以通過它來找到該記錄修改前的信息。 |
插入第一條記錄
INSERT INTO `t`(`number`, `account`) VALUES (1, '100');
假設插入該記錄的事務id
爲100
,那麼此刻該條記錄的示意圖如下所示:
可以看到插入了undo日誌中,insert undo 只是在事務回滾時有用,當事務提交後就沒用了,而且會被回收。
undo日誌說明
-
主要分爲兩種日誌
- insert undo log(只針對事務本身可見,對其他事務無影響)
- update undo log(update/delete) 也就是我們需要重點關注的知識,看下面總結圖
-
主要用於回滾,並維護原子性
-
存在與數據庫中的 undo segment(段)中
-
用於MVCC(實現非鎖定讀),讀取一行記錄時,若已被其他事務佔據,則通過undo讀取之前的版本
-
行操作(回滾行記錄到某個版本)
undo是邏輯日誌,只是將數據庫邏輯的恢復到執行語句或事務之前
什麼時候創建 undo日誌
每次對記錄進行改動,都會記錄一條undo日誌,每條undo日誌也都有一個roll_pointer屬性(INSERT操作對應的undo日誌
沒有該屬性,因爲該記錄並沒有更早的版本),可以將這些undo日誌都連起來,串成一個鏈表。
回收 undo日誌
隨着系統的運行,在確定系統中包含最早產生的那個ReadView
的事務不會再訪問某些update undo日誌
以及被打了刪除標記的記錄後,有一個後臺運行的purge線程
會把它們真正的刪除掉。
總結圖
真實字段是DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR,我們爲了美觀才寫成了row_id、transaction_id和roll_pointer。