資料庫多版本並發控制MVCC
- 2019 年 10 月 11 日
- 筆記
多版本並發控制(Multi-Version Concurrency Control, MVCC)是 MySQL 的 InnoDB 存儲引擎實現隔離級別的一種具體方式,用於實現提交讀和可重複讀這兩種隔離級別。而未提交讀隔離級別總是讀取最新的數據行,無需使用 MVCC。可串列化隔離級別需要對所有讀取的行都加鎖,單純使用 MVCC 無法實現。
MVCC提供並發訪問資料庫時,對事務內讀取的到的記憶體做處理,用來避免寫操作堵塞讀操作的並發問題。MVCC可以在大多數情況下代替行級鎖,使用MVCC,能降低其系統開銷。
一、創建表結構
資料庫表創建時,內部的隱藏列ROW_ID(行號)、DB_TRX_ID(事務id)、DB_ROLL_PTR(回滾指針);行號,模擬數據的存在的地址,事務ID,存放事務ID,回滾指針,上次提交數據事務的ID,方便回滾,類似鏈表的指針,指向上一條數據。
create table mvcc ( -- INNODB 隱藏列 ROW_ID bigint not null auto_increment primary key, -- 行號,模擬指針地址 MY_DB_TRX_ID int not null, -- 事務id MY_DB_ROLL_PTR bigint, -- 回滾指針 DELETED bit, -- 刪除標識 -- 真實欄位 id bigint, -- 主鍵id name varchar(32) -- 名稱 ) charset 'utf8', engine 'innodb';
二、初始化數據
insert into mvcc (MY_DB_TRX_ID, MY_DB_ROLL_PTR, DELETED, id, name) values (1, null, null, 1, '用來修改'), (2, null, null, 2, '用來刪除'), (3, null, null, 3, 'test');

行號ID模擬,事務ID,處理該條記錄的事務的ID,該條數據的事務ID為累加,不可縮減。回滾指針為空,是因為該條數據沒有上一次事務,因此回滾指針為空。id、name為用戶能夠看到的數據。
三、模擬查詢數據
舉例,當累計查詢,修改後,事務ID到目前為11;即MY_DB_TRX_ID = 11;
目標:查詢ID = 1的數據
要執行的sql:select * from mvcc where id = 1;
1.實際內部查詢的邏輯,查看比當前事務ID小的最近的一條ID = 1的數據,翻譯成sql為:
select * from mvcc where id = 1 and MY_DB_TRX_ID <= 11 order by MY_DB_TRX_ID desc limit 1;
查詢結果:

四、事務ID = 12修改數據
當前事務ID = 12 ,修改ID = 1的數據
要執行的sql: update mvcc set name = '修改後的數據' where id = 1;
1.查詢到滿足條件的行
select ROW_ID from mvcc where id = 1 and MY_DB_TRX_ID <= 12 order by MY_DB_TRX_ID desc limit 1

2.複製目標數據
insert into mvcc (MY_DB_TRX_ID, MY_DB_ROLL_PTR, DELETED, id, name) select MY_DB_TRX_ID, MY_DB_ROLL_PTR, DELETED, id, name from mvcc where ROW_ID = 1;
執行結果:

3.修改數據,並將DB_TRX_ID改為當前事務id,將當前行DB_ROLL_PTR指向複製的行
update mvcc set name = '修改後的數據', MY_DB_TRX_ID = 12, MY_DB_ROLL_PTR = 4 where ROW_ID = 1;
執行結果:

4.當事務12查詢數據時
執行的思路,翻譯為sql為:
select * from mvcc where id = 1 and MY_DB_TRX_ID <= 12 order by MY_DB_TRX_ID desc limit 1;

查到的數據是正常的更新後的數據。id = 1對應的內容是「修改後的數據」;
5.當事務11查詢時
select * from mvcc where id = 1 and MY_DB_TRX_ID <= 11 order by MY_DB_TRX_ID desc limit 1;

查詢到的是原本的數據id = 1對應的內容為:「用來修改」。保證了可重複讀。
五、事務12刪除數據
要執行的sql:delete from mvcc where id = 2;
1.1、查詢到滿足條件的行並複製一份
select ROW_ID from mvcc where id = 2 and MY_DB_TRX_ID <= 12 and DELETED is null order by MY_DB_TRX_ID desc limit 1; insert into mvcc (MY_DB_TRX_ID, MY_DB_ROLL_PTR, DELETED, id, name) select MY_DB_TRX_ID, MY_DB_ROLL_PTR, DELETED, id, name from mvcc where ROW_ID = 2; -- 5
執行結果:

2.當前行打上刪除標記,並將DB_TRX_ID改為當前事務id,將當前行DB_ROLL_PTR指向複製的行
update mvcc set DELETED = true, MY_DB_TRX_ID = 12, MY_DB_ROLL_PTR = 5 where ROW_ID = 2;
執行結果:

3.事務12查詢
select * from mvcc where id = 2 and MY_DB_TRX_ID <= 12 order by MY_DB_TRX_ID desc limit 1;
執行結果:已刪除數據

4.事務11查詢
select * from mvcc where id = 2 and MY_DB_TRX_ID <= 11 order by MY_DB_TRX_ID desc limit 1;
執行結果:正常查看

保證了數據可重複讀的特性。
六.事務12新增數據
要執行的sql:insert into mvcc(name) values ('新增的數據');
1.獲取自增id(4) 並插入數據
insert into mvcc(my_db_trx_id, my_db_roll_ptr, deleted, id, name) values (12, null, null, 4, '新增的數據');
執行結果:

2.事務12查詢數據
select * from mvcc where id = 4 and MY_DB_TRX_ID <= 12 order by MY_DB_TRX_ID desc limit 1;
事務12本身查詢結果:

3.事務11查詢數據
select * from mvcc where id = 4 and MY_DB_TRX_ID <= 11 order by MY_DB_TRX_ID desc limit 1; 事務11查詢結果:

查詢不到新增的數據。
疑問:事務12啟動後添加了一條數據,事務13啟動,為什麼事務13看不到事務12新增的數據?


具體當前事務或被該事務id使用快照,記錄起來,在查詢時,會根據自身當前事務ID,對比快照中,該數據的最大,最小事務ID,判斷是否在事務進程中,如果事務進程中,會根據回滾ID,查詢上次修改記錄,直至查詢快照中不含該事務ID,從而返回已提交事務的ID。