【Mysql】一篇文章理清事務隔離級別實現原理MVCC

目錄

0.事務隔離級別

1.MVCC

2.版本控制算法

2.1 select 查詢數據僞代碼

2.2 可見性算法


0.事務隔離級別

我們都知道mysql innodb引擎支持事務,爲了兼顧事務併發時之間的隔離性(ACID)和性能,提供了以下4種隔離級別及其對應的問題:

隔離級別 問題&優勢 問題解釋
讀未提交

髒、不可重複、幻讀

性能最高

髒讀:事務A讀取事務B改寫但未提交的數據,如果回滾,則讀到無效數據。

不可重複讀:事務A多次讀取同一數據返回的結果不同 (update)

幻讀:事務A讀取幾行記錄後,事務B插入(insert)一些記錄。後來的查詢中,事務A發現有些原來沒有的記錄。

讀已提交

不可重複、幻讀

性能較高

可重複讀

REPEATABLE-READ

幻讀

性能平衡 默認選擇

串行

null

依次執行,不會併發 性能差

那這4中隔離級別是如何實現的呢?

很顯然讀未提交,innodb引擎啥也沒幹,所有事務都是讀取數據最新的記錄

串行化也很好理解,就是不允許併發了,加鎖互斥訪問即可,同一數據同時刻僅能被一個事務修改,也不會有以上問題

中間的兩種讀已提交和可重複讀是用的MVCC多版本併發控制來實現的,在保證併發的數據安全問題,也提升了讀寫線程的活躍性。具體的下面詳解~

1.MVCC

  • MVCC (Mutil-Version Concurrency Control,多版本併發控制),多個事務併發進行時,對於每行每個事務有自己的Read View版本,這樣就無需給行加讀鎖,就不會阻塞寫操作,以獲得更大的併發度
  • 多版本,一條記錄存在這多個版本,某時刻最新的版本存儲在數據頁上,歷史版本存儲在undo log回滾段中,每行數據和歷史版本有隱藏列DATA_TRX_IDDATA_ROLL_PTR和舊數據。

DATA_TRX_ID表示更新這個版本的事務id(版本號);

DATA_ROLL_PTR指向上個版本,構成版本鏈

 

  • trx_id事務id,每個事務begin開始時獲取的全局唯一的連續遞增的序列號(也稱爲版本號)
  • Read View 視圖,每個事務(RR,可重複讀)或每條語句(RC,讀已提交)會在開始時創建的結構體,用於版本可見性控制包含
creator_trx_id 當前事務id
up_limit_id 活躍的最小事務id
low_limit_id 活躍的最大事務id
trx_ids 活躍事務列表

2.版本控制算法

  • time1:開啓事務,掃描事務鏈表trx_sys(當前活躍的事務),選取事務id構建Read View
creator_trx_id 當前事務id
up_limit_id 活躍的最小事務id
low_limit_id 活躍的最大事務id
trx_ids 活躍事務列表
  • time 2: select 查詢數據 ,從數據頁的數據行(版本鏈的head版本)開始遍歷,如果該版本可見(比較版本號和ReadView)返回,如果不可見走到下一跳(上個版本)。
  • times:可見? 比較版本的DATA_TRX_ID和up_limit_id、low_limit_id,判斷DATA_TRX_ID在不在trx_ids列表中,得出結論當前事務能否看見這個版本。

2.1 select 查詢數據僞代碼

select(row, readView){
    node = row //數據頁行記錄
    while(node.roll_ptr!=null 
            && canSee(node.trx_id, readView)) { // 順着版本鏈找到最新的可見的版本
        node = *roll_ptr
    }
    return node.oldVal // 版本值
}

2.2 可見性算法

如下圖,每個箭頭表示一個事務的開始和提交,按照時間順序從上到下

  1. 事務A當前事務R開始之前 提交 可見
  2. 事務B當前事務R開始之後才 開始 不可見
  3. 事務C當前事務R開始之前開始,但在檢查可見之前提交,根據事務隔離級別有不同的可見性
    1. RR 可重複讀,不可見
    2. RC 讀已提交, 可見
canSee(trx_id, readView){
    if(trx_id < readView.up_limit_id) //這些數據在事務創建id的時都已經提交
        return true; 	
    if(trx_id >= readView.low_limit_id) //該事務在當前事務開始後開始
        return false
    if(up_limit_id<trx_id<low_limit_id){
        if(隔離級別==可重複讀RR) 
            return false
        if(隔離級別==讀已提交RC) {
            if(read_view->trx_ids.contains(trx_id))
                return false //修改當前版本的事務還活躍,未提交,不可見
            else{
                return true // 該事務已提交,RC可讀
            }
        }
    }
}

 

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