一、事務概念 事務是一組操作的集合,他是一個不可分割的工作單位,事務會把所有操作作為一個整體一起向系統提交或者撤銷請求操作,即這些操作要麼同時成功,要麼同時失敗。 二、事務特性 原子性(Atomicity):事務是不可分割的最小操作單元,要麼全部成功,要麼全部失敗 一致性(Consistency): ...
一、事務概念
事務是一組操作的集合,他是一個不可分割的工作單位,事務會把所有操作作為一個整體一起向系統提交或者撤銷請求操作,即這些操作要麼同時成功,要麼同時失敗。
二、事務特性
- 原子性(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生成一條記錄版本鏈,鏈表的頭部是最新的舊記錄,鏈表尾部是最早的舊記錄
說明1:第一次insert的數據,此時DB_TRX_ID 為1, 因為還沒有修改過,所以DB_ROLL_PTR為null
說明2:此時我們模擬幾個併發同時開始的事務
說明3:此時我們在事務2中修改id為30的數據
說明4:那麼在執行修改操作之前,會先把原始數據寫入Undo Log日誌中一份,方便數據回滾
說明5:在此時原始記錄中age改為3,並且隱藏欄位DB_TRX_ID變為2,DB_ROLL_PTR在為Undo Log 日誌中的第一條數據的地址
說明6:此時事務2提交事務之後,事務3又修改id為30的數據,name改為A3
說明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(排他鎖)都是一種當前讀。
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 記錄中的三個隱藏欄位
示例如下:
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
說明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數據查詢演示
- 使用DB_TRX_ID=4 的數據進行驗證,4 不等於 creator_trx_id(5),所以第一個原則不滿足
- 使用DB_TRX_ID=4 的數據進行驗證,4 不小於 min_trx_id(3), 所以第二個原則也不滿足
- 使用DB_TRX_ID=4 的數據進行驗證,4 不大於 max_tr_id(6), 滿足第三個原則,但是第三個原則是判斷不能訪問的,就算著這裡滿足也不一定能訪問,還要看第四個原則
- 使用DB_TRX_ID=4 的數據進行驗證,4 滿足 >=3 並且 <=6 , 但是4在m_ids[3,4,5]中,所以ReadVIew1不能使用DB_TRX_ID = 4 的數據,通過表格也可以看出開在ReadView1查詢的時候,事務4並沒有提交呢
- 使用DB_TRX_ID=3 的數據進行驗證,3 不等於 creator_trx_id(5),所以第一個原則不滿足
- 使用DB_TRX_ID=3 的數據進行驗證,3 不小於 min_trx_id(3), 所以第二個原則也不滿足
- 使用DB_TRX_ID=3 的數據進行驗證,3 不大於 max_tr_id(6), 滿足第三個原則,但是第三個原則是判斷不能訪問的,就算著這裡滿足也不一定能訪問,還要看第四個原則
- 使用DB_TRX_ID=3 的數據進行驗證,3 滿足 >=3 並且 <=6 , 但是3在m_ids[3,4,5]中,所以ReadVIew1不能使用DB_TRX_ID = 3 的數據,通過表格也可以看出開在ReadView1查詢的時候,事務3並沒有提交呢
- 使用DB_TRX_ID=2 的數據進行驗證,2 不等於 creator_trx_id(5),所以第一個原則不滿足
- 使用DB_TRX_ID=2 的數據進行驗證,2 小於 min_trx_id(3), 所以第二個原則滿足,則可以判斷ReadView1使用的是 DB_TRX_ID = 2的數據,即id=30, age=3,name=A30。
說明5:ReadView2數據查詢演示
- 使用DB_TRX_ID=4 的數據進行驗證,4 不等於 creator_trx_id(5),所以第一個原則不滿足
- 使用DB_TRX_ID=4 的數據進行驗證,4 不小於 min_trx_id(4), 所以第二個原則也不滿足
- 使用DB_TRX_ID=4 的數據進行驗證,4 不大於 max_tr_id(6), 滿足第三個原則,但是第三個原則是判斷不能訪問的,就算著這裡滿足也不一定能訪問,還要看第四個原則
- 使用DB_TRX_ID=4 的數據進行驗證,4 滿足 >=4 並且 <=6 , 但是4在m_ids[4,5]中,所以ReadVIew2不能使用DB_TRX_ID = 4 的數據,通過表格也可以看出開在ReadView2查詢的時候,事務4並沒有提交呢
- 使用DB_TRX_ID=3 的數據進行驗證,3 不等於 creator_trx_id(5),所以第一個原則不滿足
- 使用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
說明6:因為這裡的數據查詢演示過程個上面的一樣,只不過這裡只有一個ReadVIew,過程更簡單。
八、事務的實現原理
8.1 MVCC的實現原理
三個隱藏欄位+Undo Log版本鏈+ReadView是MVCC的實現原理
8.2 事務隔離性的實現原理
MVCC+鎖就是事務隔離性的實現原理
8.3 事務原子性、一致性實現原理
Undo Log + Read Log
8.4 事務的持久性實現原理
Read Log
侯哥語錄:我曾經是一個職業教育者,現在是一個自由開發者。我希望我的分享可以和更多人一起進步。分享一段我喜歡的話給大家:"我所理解的自由不是想乾什麼就乾什麼,而是想不幹什麼就不幹什麼。當你還沒有能力說不得時候,就努力讓自己變得強大,擁有說不得權利。"