MVCC機制通過undo log鏈和ReadView機制來實現;
undo log版本鏈:
在資料庫的每行記錄中,都有兩個隱藏欄位,trx_id
和roll_pointer
,trx_id就是最近一次更新這個記錄的事務id,roll_pointer就是指向這個事務生成之前的那個事務的undo_log回滾日誌;
比如,當前事務A插入了一條新記錄,這個記錄的trx_id就是事務A的id,roll_pointer指向的就是一個空的undo log;
然後,來了一個事務B,事務B更新了這條記錄,那麼這個記錄的trx_id就是事務B的id,roll_pointer指向的就是之前那個事務的undo log;
這樣,多個事務更新這個記錄的時候,每次都會更新trx_id的值,並形成一個undo_log版本鏈;
ReadView機制:
當執行一個事務的時候,就會生成一個ReadView,裡面存放了四條資料:
m_ids:記錄的是此時正在執行的事務id
min_trx_id:記錄的是m_ids中的最小的事務id
max_trx_id:記錄的是m_ids中的最大的事務id+1,就是下一個會生成的事務id
creator_trx_id:記錄的是本事務的id
比如,此時有一條記錄,trx_id是10;這時候生成了事務A(id=20)想要讀資料,事務B(id=30)想要寫資料,那麼事務A生成的ReadView中,m_ids就是[20,30],min_trx_id=20,max_trx_id=31,creator_trx_id=20;
此時,事務A去查詢這條記錄,發現trx_id=10,小於自己的min_trx_id,這就說明這個資料在事務A生成之前就被更新過了,可以放心讀;
然後,事務B去更新了這個記錄,這個記錄的trx_id就會變為30,然後事務A再去查詢,發現trx_id=30,在自己的min_trx_id和max_trx_id之間,這就說明這個記錄被一個和自己在差不多時間執行的事務修改過,然後再去檢視這個trx_id=30在不在自己的m_ids中,發現在裡面,說明修改這個資料的事務是和自己併發執行的,所以,這個資料就是不能讀的,只能讀到roll_pointer鏈上的上一次trx_id=10的資料;
通過ReadView和undo_log日誌鏈,就可以保證事務A不會讀到併發執行的事務B修改的資料;
假設,然後事務A又自己去更新了這條資料,那麼再查詢,記錄的trx_id就會是20,和自己的creator_trx_id一樣,也就是說是自己改的,當然是可以讀的;
假設,又來了一個事務C(id=40),去更新這個記錄,那麼事務A再次查詢,發現trx_id=40,比自己的max_trx_id大,那就說明資料被一個新的事務修改了,當然也不能讀,只能順著undo log鏈往前讀資料;
通過ReadView機制和undo_log日誌鏈,就可以判斷當前記錄的哪個版本是我們可以讀的。
RC隔離級別如何基於ReadView機制實現:
讀提交隔離級別,在每次查詢的時候,都會生成一個新的ReadView;
當事務A查詢一個記錄的時候,產生一個新的ReadView,如果這個記錄的trx_id在自己的min_trx_id和max_trx_id之間,並且在自己的m_ids裡,那說明這個記錄被一個還沒提交的事務修改了,當然不可讀;
如果事務B提交了修改,那麼事務B就不會出現在事務A的ReadView,當然就可以讀了;
RR隔離級別如何基於ReadView機制實現:
在可重複讀級別下,會在事務中的第一次查詢的時候,生成一個ReadView,之後事務裡的查詢都用這個ReadView,但是可以自己更新;
當事務A查詢一個記錄的時候,產生一個ReadView,此時讀到第一次資料,如果這時候事務B修改了資料,那麼記錄的trx_id就會更新,這時候事務A再查詢資料,用的還是第一次的ReadView,所以即使事務B已經提交了,事務A讀到trx_id的時候,這個trx_id肯定是要麼大於max_trx_id,要麼還在m_ids裡,所以不會讀這個資料,會順著undo_log鏈去找之前的資料,因此讀到的還是第一次資料,實現可重複讀;
當然,如果是事務A自己修改了資料,是可以讀到的;