MVCC多版本並發控制器

  • 2021 年 2 月 28 日
  • 筆記

在多個事務並發執行的時候,MVCC機制可以協調數據的可見性,事務的隔離級別就是建立在MVCC之上的;

MVCC機制通過undo log鏈和ReadView機制來實現;

undo log版本鏈:

 

 

在資料庫的每行記錄中,都有兩個隱藏欄位,trx_idroll_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自己修改了數據,是可以讀到的;