14 MVCC的可見性

轉自http://hedengcheng.com/?p=148 何登成

一、準備

create table test (id int primary key, comment char(50)) engine=innodb;

create index test_idx on test(comment);

insert into test values (1, ‘aaa’);

insert into test values (2, ‘bbb’);


更新主鍵update test set id = 9 where id = 1;

更新非主鍵update test set comment = ‘ccc’ where id = 9;


總結:
a、無論是聚簇索引,還是二級索引,只要其鍵值更新,就會產生新版本。將老版本數據deleted bti設置爲1;同時插入新版本。
b、對於聚簇索引,如果更新操作沒有更新primary key,那麼更新不會產生新版本,而是在原有版本上進行更新,老版本進入undo表空間,通過記錄上的undo指針進行回滾。
c、對於二級索引,如果更新操作沒有更新其鍵值,那麼二級索引記錄保持不變。
d、對於二級索引,更新操作無論更新primary key,或者是二級索引鍵值,都會導致二級索引產生新版本數據。
e、聚簇索引設置記錄deleted bit時,會同時更新DATA_TRX_ID列。老版本DATA_TRX_ID進入undo表空間;二級索引設置deleted bit時,不寫入undo。


二、可見性判斷

A、主鍵查找
1、select * from test where id = 1;
針對測試1,如果1811(DATA_TRX_ID) < read_view.up_limit_id,證明被標記爲刪除的記錄1可見。刪除可見 -> 無記錄返回。
針對測試1,如果1811(DATA_TRX_ID) >= read_view.low_limit_id,證明被標記爲刪除的記錄1不可見,通過DATA_ROLL_PTR回滾記錄,得到DATA_TRX_ID = 1809。如果1809可見,則返回記錄(1,aaa);否則無記錄返回。
針對測試1,如果up_limit_id,low_limit_id都無法判斷可見性,那麼遍歷read_view中的trx_ids,依次對比事務id,如果在DATA_TRX_ID在trx_ids數組中,則不可見(更新未提交)。


2、select * from test where id = 9;
針對測試2,如果1816可見,返回(9,ccc)。
針對測試2,如果1816不可見,通過DATA_ROLL_PTR回滾到1811,如果1811可見,返回(9, aaa)。
針對測試2,如果1811不可見,無結果返回。

3、select * from test where id > 0;
針對測試1,索引中,滿足條件的同一記錄,有兩個版本(版本1,delete bit =1)。那麼是否會一條記錄返回兩次呢?必定不會,這是因爲pk = 1的可見性與pk = 9的可見性是一致的,同時pk = 1是標記了deleted bit的版本。如果事務ID = 1811可見。那麼pk = 1 delete可見,無記錄返回,pk = 9返回記錄;如果1811不可見,回滾到1809可見,那麼pk = 1返回記錄,pk = 9回滾後無記錄。
 
總結:
a、通過主鍵查找記錄,需要配合read_view,記錄DATA_TRX_ID,記錄DATA_ROLL_PTR指針共同判斷。
b、read_view用於判斷當前記錄是否可見(判斷DATA_TRX_ID)。DATA_ROLL_PTR用於將當前記錄回滾到前一版本。

B、非主鍵查找
1、select comment from test where comment > ‘ ‘;
針對測試2,二級索引,當前頁面的最大更新事務MAX_TRX_ID = 1816。如果MAX_TRX_ID < read_view.up_limit_id,當前頁面所有數據均可見,本頁面可以進行索引覆蓋性掃描。丟棄所有deleted bit = 1的記錄,返回deleted bit = 0 的記錄;此時返回 (ccc)。(row_select_for_mysql -> lock_sec_rec_cons_read_sees)
針對測試2,二級索引,如果當前頁面不能滿足MAX_TRX_ID < read_view.up_limit_id,說明當前頁面無法進行索引覆蓋性掃描,此時需要針對每一項,到聚簇索引中判斷可見性。回到測試2,二級索引中有兩項pk = 9 (一項deleted bit = 1,另一個爲0),對應的聚簇索引中只有一項pk= 9。如何保證通過二級索引過來的同一記錄的多個版本,在聚簇索引中最多隻能被返回一次?如果當前事務id 1811可見。二級索引pk = 9的記錄(兩項),通過聚簇索引的undo,都定位到了同一記錄項。此時,InnoDB通過以下的一個表達式,來保證來自二級索引,指向同一聚簇索引記錄的多個版本項,有且最多僅有一個版本將會返回數據:
if (clust_rec
&& (old_vers || rec_get_deleted_flag(
                      rec,dict_table_is_comp(sec_index->table)))
         && !row_sel_sec_rec_is_for_clust_rec(rec, sec_index, clust_rec, clust_index))

滿足if判斷的所有聚簇索引記錄,都直接丟棄,以上判斷的邏輯如下:
a、需要回聚簇索引掃描,並且獲得記錄
b、聚簇索引記錄爲回滾版本,或者二級索引中的記錄爲刪除版本
c、聚簇索引項,與二級索引項,其鍵值並不相等
爲什麼滿足if判斷,就可以直接丟棄數據?用白話來說,就是我們通過二級索引記錄,定位聚簇索引記錄,定位之後,還需要再次檢查聚簇索引記錄是否仍舊是我在二級索引中看到的記錄。如果不是,則直接丟棄;如果是,則返回。

根據此條件,結合查詢與測試2中的索引結構。可見版本爲事務1811.二級索引中的兩項pk = 9都能通過聚簇索引回滾到1811版本。但是,二級索引記錄(ccc,9)與聚簇索引回滾後的版本(aaa,9)不一致,直接丟棄。只有二級索引記錄(aaa,9)保持一致,直接返回。

總結:
a、二級索引的多版本可見性判斷,需要通過聚簇索引完成。
b、二級索引頁面中保存了MAX_TRX_ID,可以快速判斷當前頁面中,是否所有項均可見,可以實現二級索引頁面級別的索引覆蓋掃描。一般而言,此判斷是滿足條件的,保證了索引覆蓋掃描 (index only scan)的高效性。
c、二級索引中的項,需要與聚簇索引中的可見性進行比較,保證聚簇索引中的可見項,與二級索引中的項數據一致。


備註:相關結構


關於readview:

low_limit_no
• 提交時間早於此值(trx->no < low_limit_no)的事務,可以被purge線程回收

low_limit_id
• >= 此值(trx->id >= low_limit_id)的事務,當前ReadView均不可見

up_limit_id
• < 此值(trx->id < up_limit_id)的事務,當前ReadView一定可見
• up_limit_id = ReadView創建時系統中最小活躍事務ID(一般的,low_limit_id>up_limit_id

trx_ids[]
• 系統中所有活躍事務id組成的數組

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章