MySQL高階12-事務原理

Se7eN_HOU發表於2023-09-20

一、事務概念

  事務是一組操作的集合,他是一個不可分割的工作單位,事務會把所有操作作為一個整體一起向系統提交或者撤銷請求操作,即這些操作要麼同時成功,要麼同時失敗。

二、事務特性

  • 原子性(Atomicity):事務是不可分割的最小操作單元,要麼全部成功,要麼全部失敗
  • 一致性(Consistency):事務完成時,必須是所有的資料保持一致性狀態
  • 隔離性(Isolation):資料庫系統提供的隔離機制,保證事務在不受外部併發操作影響的獨立環境下執行
  • 永續性(Durability):事務一旦提交或回滾,他對資料庫中的資料的該表就是永久的

三、事務的隔離級別

  • 讀未提交(Read Uncommitted):最低的隔離級別,事務可以讀取到其他事務尚未提交的資料,存在髒讀(Dirty Read)問題,即讀取到未提交的資料,可能導致資料的不一致性。
  • 讀已提交(Read Committed):事務只能讀取到其他事務已經提交的資料,解決了髒讀的問題,但是可能導致不可重複讀(Non-Repeatable Read)問題,即在同一事物中多次讀取同一資料時可能會得到不同的結果。
  • 可重複讀(Repeatable Read):可重複讀是MySQL預設的隔離級別,並且解決了不可重複讀問題,在一個事務中,多次讀取同一資料會得到相同的結果,及時其他事務更改了資料。
  • 序列化讀(Serializable):最高的隔離級別,強制所有事務按照順序依次執行,避免了髒讀,不可重讀和幻讀(Phantom Read),幻讀指在同一事務中,多次查詢同一個範圍的資料時,結果集合的行數可能不一致。

四、Redo Log

  在MySQL中,Redo Log和Undo Log是用來支援事務和保證資料一致性的關鍵日誌機制。

  Redo Log(重做日誌): 作用是記錄了所有對資料庫的修改操作,包括插入、更新和刪除等操作。記錄了事務提交時資料頁的物理修改,是用來實現事務的永續性

  該日誌檔案有兩部分組成:重做日誌緩衝(Redo Log Buffer)以及重做日誌檔案(Redo Log File),前者是在記憶體中,後者在磁碟中,當事務提交之後會把所有資訊都存到該日誌檔案中,使用者重新整理髒頁到磁碟,發生錯誤時,進行資料恢復使用。

  工作原理:當事務進行資料修改時,MySQL將修改的操作記錄到 Redo Log中,而不直接寫入磁碟的資料檔案中。這樣可以減少磁碟IO的操作,提高效能。在事務提交時,Redo Log的內容會被非同步重新整理到磁碟上的資料檔案中,確保資料的永續性。如果系統崩潰,MySQL可以透過Redo Log中的資訊重做之前未寫入磁碟的修改操作,恢復到事務提交的狀態。

五、Undo Log

  5.1 簡介

    Undo Log(回滾日誌):用來支援事務的回滾操作,即將事務的修改操作撤銷,恢復到之前的狀態。在insert、update、delete的時候產生的便於資料回滾的日誌。

    當insert的時候,產生的 Undo Log日誌只在回滾時需要,在事務提交後,可被立即刪除。

    而update、delete的時候,產生的Undo Log日誌不僅在回滾時需要,在快照讀時也需要,不會立即被刪除。

    工作原理:當事務進行資料修改時,MySQL將修改的操作記錄到Undo Log中,並在事務提交之前保留這些修改的記錄。如果事務發生回滾操作,MySQL會根據Undo Log中的資訊,將事務的修改操作撤銷,將資料還原到事務開始的狀態。因此,Undo Log為事務提供了撤銷操作的能力,確保資料庫的一致性。

  5.2 Undo Log 版本鏈

    不同事務或相同事務對同一條記錄進行修改,會導致該記錄的Undo Log生成一條記錄版本鏈,連結串列的頭部是最新的舊記錄,連結串列尾部是最早的舊記錄

    MySQL高階12-事務原理

     說明1:第一次insert的資料,此時DB_TRX_ID 為1, 因為還沒有修改過,所以DB_ROLL_PTR為null

    MySQL高階12-事務原理

     說明2:此時我們模擬幾個併發同時開始的事務

    MySQL高階12-事務原理

     說明3:此時我們在事務2中修改id為30的資料

    MySQL高階12-事務原理

    說明4:那麼在執行修改操作之前,會先把原始資料寫入Undo Log日誌中一份,方便資料回滾

    說明5:在此時原始記錄中age改為3,並且隱藏欄位DB_TRX_ID變為2,DB_ROLL_PTR在為Undo Log 日誌中的第一條資料的地址

    MySQL高階12-事務原理

     說明6:此時事務2提交事務之後,事務3又修改id為30的資料,name改為A3

  MySQL高階12-事務原理

    說明7:此時在更改原始資料之前,需要把當前的資料在寫入到Undo Log日誌中一份,繼續方便做資料回滾,而這時Undo Log中就有了兩條資料

    說明8:然後就可以修改原始資料 name 改為 A3,並且DB_TRX_ID也改為3,DB_ROLL_PTR指向Undo Log第二條日誌的地址。

    說明9:這樣就形成了一條完整的Undo Log版本鏈 

六、MVCC

  6.1 當前讀

    讀取的是記錄的最新版本,讀取時還要保證其他併發事務不能修改當前記錄,會對讀取的記錄進行枷鎖。

    如:select...lock in share mode(共享鎖)、select...for update、update、insert、delete(排他鎖)都是一種當前讀。

    MySQL高階12-事務原理

   6.2 快照讀

    簡單的select(不加鎖)就是快照讀,快照讀讀取的是記錄資料的可見版本,有可能是歷史資料,不加鎖,是非阻塞讀。

    • Read Committed:每次select,都生成一個快照讀。
    • Repeatable Read:開啟事務後第一個select語句就會生成一個快照讀,其後面的select讀取的都是快照中的資料,所以資料才是可重複讀的。
    • Serializable:快照讀會退化為當前讀      

  6.3 MVCC

    全稱 Multi-Version Concurrency Control 多版本併發控制。指維護一個資料的多個版本,使得讀寫操作沒有衝突,快照讀為MySQL實現MVCC提供了一個非阻塞讀功能。MVCC具體實現,還需要依賴於資料庫記錄中的三個隱式欄位、Undo Log日誌、readView。

  6.4 記錄中的三個隱藏欄位   

    示例如下: 

   MySQL高階12-事務原理

    DB_TRX_ID:最近修改事務ID,記錄插入這條記錄或最後一次修改該記錄的事務ID

    DB_ROLL_PTR:回滾指標,指向這條記錄的上一個版本,用於配合Undo Log,指向上一個版本

    DB_ROW_ID:隱式主鍵,如果表結構沒有指定主鍵,將會生成該隱藏欄位

    使用指令重新建立一個表mvcc_test,這個時候不給該表設定主鍵

    

    然後在資料庫路徑下的data資料夾下就可以找到我們剛建立的獨立表結構空間mvcc_test.ibd

    

     MySQL提供了一個ibd2sdi 指令可以檢視錶結構空間

root@ubuntu:/usr/local/mysql/data/mysql_test# ibd2sdi mvcc_test.ibd 
["ibd2sdi",
{
    ...                                     # 這裡的 ... 是我過濾掉的無關資料
    "dd_object_type": "Table",              # 這裡說明這是一個表結構
    "dd_object": {
        "name": "mvcc_test",           # 名稱 mvcc_test
        ...
        "columns": [                        # 所有的列
            {
                "name": "name",             # 自己定義的name列
               ... 
            },
            {
                "name": "DB_ROW_ID",         # 隱式建立的列
                ...
            },
            {
                "name": "DB_TRX_ID",         # 隱式建立的列,如果在建立表的時候指定了主鍵,則該隱藏欄位就不會被建立
                 ...
            },
            {
                "name": "DB_ROLL_PTR",       # 隱式建立的列
                 ...
            }
        ],
        "schema_ref": "mysql_test",          # 該表所屬的資料庫
      ...
    }
}
] root@ubuntu:/usr/local/mysql/data/mysql_test#

七、readView

  7.1 readView(讀檢視)

    是快照讀SQL執行時MVCC提取資料的依據,記錄並維護系統當前活躍的事務id,即未提交的事務id

  7.2 readView中的四個核心欄位

    • m_ids:當前活躍的事務ID集合,即當前未提交的事務的ID集合。
    • min_trx_id:最小活躍事務ID。
    • max_trx_id:預分配事務ID,當前最大事務ID+1,因為事務ID是自增的。
    • creator_trx_id:readView建立者的事務ID。

   7.3 版本鏈資料訪問規則

    • 當前事務ID == creator_trx_id 可以訪問該版本:因為當前事務的id=creator_trx_id說明這個版本就是當前事務建立的。
    • 當前事務ID < min_trx_id 可以訪問該版本:因為當前事務ID小於當前正在活躍事務的最小ID,說明當前事務已經提交了,並且該版本是在當前時候提交後才建立,所以可以訪問。
    • 當前事務ID > max_trx_id 不可以訪問該版本:因為當前事務ID比readView最大的活躍ID還大,說明當前事務是在readVIew建立之後才開啟的,所以不能訪問以前的還未提交事務的資料。
    • 當前事務ID >= min_trx_id 並且 <= max_trx_id 並且不在m_ids中 可以訪問該版本:因為不在m_ids中說明資料已經提交了,並且<= max_trx_id 又說明不是在readView建立之後開啟的,所以可以訪問。

  7.4 不同隔離級別,生成readView的時機不同

    • read committed:在事務中每一次執行快照讀時生成readView。
    • repeatable read:僅在事務中第一次執行快照讀時生成readView,後續複用該readView。

   7.5 read committed隔離級別下readView查詢資料的機制

    因為read committed 模式下, 在事務中每一次執行快照讀時都會生成readView.所以在同一個事務下,讀取兩次資料,就會產生兩個ReadVIew

    MySQL高階12-事務原理

 

    說明1:以事務5的兩次查詢為例

    說明2:ReadView1中的m_ids:[3,4,5],因為事務2在開啟該次查詢事務的事就已經提交了,所以m_ids中不包括2,同樣的在ReadView2建立的時候,事務3也已經提交,所以ReadView2中的m_ids只有[4,5]

    說明3:套用版本鏈訪問資料的規則,ReadView1和ReadView2兩次查詢資料演示

    說明4:ReadView1資料查詢演示

    1. 使用DB_TRX_ID=4 的資料進行驗證,4 不等於 creator_trx_id(5),所以第一個原則不滿足
    2. 使用DB_TRX_ID=4 的資料進行驗證,4 不小於 min_trx_id(3), 所以第二個原則也不滿足
    3. 使用DB_TRX_ID=4 的資料進行驗證,4 不大於 max_tr_id(6), 滿足第三個原則,但是第三個原則是判斷不能訪問的,就算著這裡滿足也不一定能訪問,還要看第四個原則
    4. 使用DB_TRX_ID=4 的資料進行驗證,4 滿足 >=3 並且 <=6 , 但是4在m_ids[3,4,5]中,所以ReadVIew1不能使用DB_TRX_ID = 4 的資料,透過表格也可以看出開在ReadView1查詢的時候,事務4並沒有提交呢
    5. 使用DB_TRX_ID=3 的資料進行驗證,3 不等於 creator_trx_id(5),所以第一個原則不滿足
    6. 使用DB_TRX_ID=3 的資料進行驗證,3 不小於 min_trx_id(3), 所以第二個原則也不滿足
    7. 使用DB_TRX_ID=3 的資料進行驗證,3 不大於 max_tr_id(6), 滿足第三個原則,但是第三個原則是判斷不能訪問的,就算著這裡滿足也不一定能訪問,還要看第四個原則
    8. 使用DB_TRX_ID=3 的資料進行驗證,3 滿足 >=3 並且 <=6 , 但是3在m_ids[3,4,5]中,所以ReadVIew1不能使用DB_TRX_ID = 3 的資料,透過表格也可以看出開在ReadView1查詢的時候,事務3並沒有提交呢
    9. 使用DB_TRX_ID=2 的資料進行驗證,2 不等於 creator_trx_id(5),所以第一個原則不滿足
    10. 使用DB_TRX_ID=2 的資料進行驗證,2 小於 min_trx_id(3), 所以第二個原則滿足,則可以判斷ReadView1使用的是 DB_TRX_ID = 2的資料,即id=30, age=3,name=A30

    說明5:ReadView2資料查詢演示

    1. 使用DB_TRX_ID=4 的資料進行驗證,4 不等於 creator_trx_id(5),所以第一個原則不滿足
    2. 使用DB_TRX_ID=4 的資料進行驗證,4 不小於 min_trx_id(4), 所以第二個原則也不滿足
    3. 使用DB_TRX_ID=4 的資料進行驗證,4 不大於 max_tr_id(6), 滿足第三個原則,但是第三個原則是判斷不能訪問的,就算著這裡滿足也不一定能訪問,還要看第四個原則
    4. 使用DB_TRX_ID=4 的資料進行驗證,4 滿足 >=4 並且 <=6 , 但是4在m_ids[4,5]中,所以ReadVIew2不能使用DB_TRX_ID = 4 的資料,透過表格也可以看出開在ReadView2查詢的時候,事務4並沒有提交呢
    5. 使用DB_TRX_ID=3 的資料進行驗證,3 不等於 creator_trx_id(5),所以第一個原則不滿足
    6. 使用DB_TRX_ID=3 的資料進行驗證,3 不小於 min_trx_id(4), 所以第二個原則滿足,則可以判斷ReadView2使用的是 DB_TRX_ID = 3的資料,即id=30, age=3,name=A3。

  7.6 repeatable read 隔離級別下readView查詢資料的機制

    repeatable read:僅在事務中第一次執行快照讀時生成readView,後續複用該readView,所以這裡只會產生一個ReadVIew

    MySQL高階12-事務原理

     說明6:因為這裡的資料查詢演示過程個上面的一樣,只不過這裡只有一個ReadVIew,過程更簡單。

八、事務的實現原理

  8.1 MVCC的實現原理

    三個隱藏欄位+Undo Log版本鏈+ReadView是MVCC的實現原理

  8.2 事務隔離性的實現原理

    MVCC+鎖就是事務隔離性的實現原理

  8.3 事務原子性、一致性實現原理

    Undo Log + Read Log

  8.4 事務的永續性實現原理

    Read Log

 

相關文章