事務的實現
redo log保證事務的永續性,undo log用來幫助事務回滾及MVCC的功能。
InnoDB儲存引擎體系結構
redo log
Write Ahead Log策略
事務提交時,先寫重做日誌再修改頁;當由於發生當機而導致資料丟失時,就可以通過重做日誌來完成資料的恢復。
- InnoDB首先將重做日誌資訊先放到重做日誌快取
- 按一定頻率重新整理到重做日誌檔案
重做日誌檔案: 在預設情況,InnoDB儲存引擎的資料目錄下會有兩個名為ib_logfile1和ib_logfile2的檔案。每個InnoDB儲存引擎至少有1個重做日誌檔案組(group),每個檔案組下至少有2個重做日誌檔案。
下面圖一,很好說明重做日誌組以迴圈寫入方式執行,InnoDB儲存引擎先寫ib_logfile1,當達到檔案最後時,會切換至重做日誌檔案ib_logfile2.
而圖2,增加一個OS Buffer,有助於理解fsync過程。
關於log group,稱為重做日誌組,是一個邏輯上的概念。InnoDB儲存引擎實際只有一個log group。
log group中第一個redo log file,其前2KB部分儲存4個512位元組大小塊:
重做日誌緩衝重新整理到磁碟
下面三種情況重新整理:
- Master Thread每一秒將重做日誌緩衝重新整理到重做日誌檔案
- 每個事務提交時會將重做日誌緩衝重新整理到重做日誌檔案
- 當重做日誌緩衝池剩餘空間小於1/2時,重做日誌重新整理到重做日誌檔案
補充上述三種情況第二種,觸發寫磁碟過程由引數innodb_flush_log_at_trx_commit控制,表示提交(commit)操作時,處理重做日誌的方式。
引數innodb_flush_log_at_trx_commit有效值有0、1、2
- 0表示當提交事務時,並不將事務的重做日誌寫入磁碟上日誌檔案,而是等待主執行緒每秒重新整理。
- 1表示在執行commit時將重做日誌緩衝同步寫到磁碟,即伴有fsync的呼叫
- 2表示將重做日誌非同步寫到磁碟,即寫到檔案系統的快取中。不保證commit時肯定會寫入重做日誌檔案。
0,當資料庫發生當機時,部分日誌未重新整理到磁碟,因此會丟失最後一段時間的事務。
2,當作業系統當機時,重啟資料庫後會丟失未從檔案系統快取重新整理到重做日誌檔案那部分事務。
下圖有助於理解
重做日誌塊
在InnoDB儲存引擎中,重做日誌都是以512位元組進行儲存的。意味著重做日誌快取、重做日誌檔案都是以塊(block)的方式進行儲存的,每塊512位元組。
重做日誌頭12位元組,重做日誌尾8位元組,故每個重做日誌塊實際可以儲存492位元組。
重做日誌格式
redo log是基於頁的格式來記錄的。預設情況下,innodb的頁大小是16KB(由 innodb_page_size變數控制),一個頁內可以存放非常多的log block(每個512位元組),而log block中記錄的又是資料頁的變化。
log body的格式分為4部分:
- redo_log_type:佔用1個位元組,表示redo log的日誌型別。
- space:表示表空間的ID,採用壓縮的方式後,佔用的空間可能小於4位元組。
- page_no:表示頁的偏移量,同樣是壓縮過的。
- redo_log_body表示每個重做日誌的資料部分,恢復時會呼叫相應的函式進行解析。例如insert語句和delete語句寫入redo log的內容是不一樣的。
如下圖,分別是insert和delete大致的記錄方式。
redo日誌恢復
下面LSN(Log Sequence Number)代表checkpoint,當資料庫在LSN為10000時發生當機,恢復操作僅恢復LSN10000-LSN13000範圍內日誌
undo log
undo log的作用
undo是邏輯日誌,只是將資料庫邏輯地恢復到原來的樣子;所有修改都被邏輯地取消了,但是資料結構和頁本身在回滾之後可能不大相同。
undo log有兩個作用:提供回滾和多個行版本控制(MVCC)。
-
InnoDB儲存引擎回滾時,對於每個INSERT,會完成一個DELETE;對於每個DELETE,會執行一個INSERT;對於每個UPDATE,會執行一個相反的UPDATE,將修改前的行放回去。
-
MVCC: 當使用者讀取一行記錄時,若該記錄已經被其他事務佔用,當前事務可以通過undo讀取之前的行版本資訊,以此實現非鎖定讀取。
undo log的儲存方式
innodb儲存引擎對undo的管理採用段的方式。rollback segment稱為回滾段,每個回滾段中有1024個undo log segment。
在以前老版本,只支援1個rollback segment,這樣就只能記錄1024個undo log segment。後來MySQL5.5可以支援128個rollback segment,即支援128*1024個undo操作,還可以通過變數 innodb_undo_logs (5.6版本以前該變數是 innodb_rollback_segments )自定義多少個rollback segment,預設值為128。
undo log預設存放在共享表空間中。
事務提交undo log處理過程
當事務提交時,InnoDB儲存引擎會做以下兩件事:
- 將undo log放入一個列表中,以供之後的purge使用,是否可以最終刪除undo log及所在頁由purge執行緒來判斷
- 判斷undo log 所在的頁是否可以重用,若可以,分配給下個事務使用
當事務提交時,首先將undo log放入連結串列中,然後判斷undo頁的使用空間是否小於3/4,若是,則表示該undo頁可以被重用,之後新的undo log記錄在當前undo log的後面
undo log分為:
- insert undo log
- update undo log
因為事務隔離性,insert undo log對其他事務不可見,所以該undo log可以在事務提交後直接刪除,不需要進行purge操作。
update undo log記錄的是對delete和update操作產生的undo log。該undo log可能需要提供MVCC機制,因此不能提交時就進行刪除
update分為兩種情況:
- update的列如果不是主鍵列,在undo log中直接反向記錄是如何update的。即update是直接進行的。
- update主鍵的操作可以分為兩步:
- 首先將原主鍵記錄標記為已刪除,因此需要產生一個型別為TRX_UNDO_DEL_MARK_REC的undo log
- 之後插入一條新的記錄,產生一個型別為TRX_UNDO_INSERT_MARK_REC的undo log
InnoDB purge時,會先從history列表找undo log,然後再從undo page中找undo log;可以避免大量隨機讀取操作,從而提高purge效率。
MVCC(多版本併發控制)
MVCC其實就是在每一行記錄後面增加兩個隱藏列,記錄建立版本號和刪除版本號,而每一個事務在啟動的時候,都有一個唯一的遞增的版本號。
MVCC只在REPEATABLE READ 和READ COMMITTED兩個隔離級別下工作。讀未提交不存在版本問題,序列化則對所有讀取行加鎖。
示例:
- 插入操作:記錄的建立版本號就是事務版本號
如插入一條記錄,事務id假設是1,則建立版本號也是1
id | name | create version | delete version |
---|---|---|---|
1 | test | 1 |
- 更新操作:先標記舊版本號為已刪除,版本號就是當前版本號,再插入一條新的記錄
如事務2把name欄位更新
update table set name = 'new test' where id = 1;
原來的記錄被標記刪除,刪除版本號為2,並插入新記錄,建立版本號為2
id | name | create version | delete version |
---|---|---|---|
1 | test | 1 | 2 |
1 | new test | 2 |
- 刪除操作:把事務版本作為刪除版本號
如事務3把記錄刪除
delete from table where id = 1;
id | name | create version | delete version |
---|---|---|---|
1 | test | 2 | 3 |
- 查詢操作
需滿足以下兩個條件的記錄才能被事務查詢出來:
- InnoDB只查詢版本早於當前事務版本的資料行
- 行的刪除版本要麼未定義,要麼大於當前版本號,這可以確保事務讀取到的行,在事務未開始之前未被刪除
MVCC好處:減少鎖的爭用,提升效能
binlog
二進位制檔案概念及作用
二進位制檔案(binary log)記錄了對MySQL資料庫執行更改的所有操作(不包含SELECT、SHOW等,因為對資料沒有修改)
二進位制檔案主要幾種作用:
- 恢復:某些資料的恢復需要二進位制日誌
- 複製: 通過複製和執行二進位制日誌使一臺遠端的MySQL(slave)與另一臺MySQL資料庫(master)進行實時同步
- 審計: 使用者可以通過二進位制日誌中資訊來進行審計,判斷是否有對資料庫進行注入的攻擊
二進位制檔案三個格式
MySQL 5.1開始引入binlog_format引數,該引數可設值有STATEMENT、ROW和MIX
- STATEMENT: 二進位制檔案記錄的是日誌的邏輯SQL語句
- ROW:記錄表的行更改情況。如果設定了ROW模式,可以將InnoDB事務隔離級別設為READ_COMMITTED,以獲得更好的併發性
- MIX:MySQL預設採用STATEMENT格式進行二進位制檔案的記錄,但在一些情況下會使用ROW,可能的情況有:
- 表的儲存引擎為NDB,這時對錶DML操作都以ROW格式進行
- 使用了UUID()、USER()、CURRENT_USER()、FOUND_ROWS()、ROW_COUNT()等不確定函式
- 使用了INSERT DELAY語句
- 使用了使用者定義函式
- 使用了臨時表
redo log和二進位制檔案區別
(二進位制檔案用來進行POINT-IN-TIME(PIT))的恢復及主從複製環境的建立。
- 二進位制檔案會記錄所有與MySQL資料庫有關的日誌記錄,包括InnoDB、MyISAM等其他儲存引擎的日誌。而InnoDB儲存引擎的重做日誌只記錄有關該儲存引擎本身的事務日誌。
- 記錄的內容不同,無論使用者將二進位制日誌檔案記錄的格式設為STATEMENT、ROW或MIXED,其記錄的都是關於一個事務的具體操作內容,即該日誌是邏輯日誌。而InnoDB儲存引擎的重做日誌檔案記錄的是關於每個頁的更改的物理情況。
- 此外,寫入的時間頁不同,二進位制日誌檔案僅再事務提交前進行提交,即只寫磁碟一次,不論這時該事務多大。而在事務進行的過程中,卻不斷有重做日誌條目(reod entry)被寫入到重做日誌檔案中。
group commit
若事務為非只讀事務,則每次事務提交時需要進行一次fsync操作,以此保證重做日誌都已經寫入磁碟。但磁碟fsync效能有限,為提高磁碟fsync效率,當前資料庫都提供group commit功能,即一次可以重新整理確保多個事務日誌被寫入檔案。
對InnoDB group commit,進行兩階段操作:
- 修改記憶體中事務對應的資訊,並且將日誌寫入重做日誌緩衝
- 呼叫fsync將確保日誌都從重做日誌緩衝寫入磁碟
InnoDB1.2前,開啟二進位制檔案,group commit功能失效問題:
開啟二進位制檔案後,其步驟如下:
1)當事務提交時,InnoDB儲存引擎進行prepare操作
2)MySQL資料庫上層寫入二進位制檔案
3)InnoDB將日誌寫入重做日誌檔案
- a)修改記憶體中事務對應的資訊,並將日誌寫入重做日誌緩衝
- b)呼叫fsync將確保日誌都從重做日誌緩衝寫入磁碟
其中在保證MySQL資料庫上層二進位制檔案的寫入順序,和InnoDB事務提交順序一致,MySQL內部使用了prepare_commit_mutex鎖,從而步驟3)中a)步不可以在其他事務執行步驟b)時進行,從而導致roup commit功能失效。
解決方案便是BLGC(Binary Log Group Commit)
MySQL 5.6 BLGC實現方式分為三個階段:
- Flush階段:將每個事務的二進位制檔案寫入記憶體
- Sync階段:將記憶體中的二進位制重新整理到磁碟,若佇列有多個事務,那麼僅一次fsync操作就完成了二進位制日誌的寫入,這就是BLGC
- Commit階段:leader根據順序呼叫儲存引擎層事務提交,由於innodb本就支援group commit,所以解決了因為鎖 prepare_commit_mutex 而導致的group commit失效問題。
參考:
《MySQL技術內幕》
https://mp.weixin.qq.com/s/rNFy_qwnNWUvzjYznOXKJw
https://www.cnblogs.com/hapjin/archive/2019/09/28/11521506.html