資料庫篇:mysql日誌型別之 redo、undo、binlog

潛行前行發表於2022-03-28

前言

可以說mysql的多數特性都是圍繞日誌檔案實現,而其中最重要的有以下三種

  • redo 日誌
  • undo 日誌
  • binlog 日誌

關注公眾號,一起交流;微信搜一搜: 潛行前行

1 redo日誌

innodb 為了提高磁碟I/O讀寫效能,存在一個 buffer pool 的記憶體空間,資料頁讀入會快取到 buffer pool,事務的提交則實時更新到 buffer pool,而不實時同步到磁碟(innodb 是按 16KB 一頁同步的,一事務可涉及多個資料頁,實時同步會造成浪費,隨機I/O)。事務暫存在記憶體,則存在一致性問題,為了解決系統崩潰,保證事務的永續性,我們只需把事務對應的 redo 日誌持久化到磁碟即可(redo 日誌佔用空間小,順序寫入磁碟,順序I/O)

Mini-Transaction (MTR)

  • sql 語句在執行的時候,可能會修改多個頁面,還會更新聚簇索引和二級索引的頁面,過程產生的redo會被分割成多個不可分割的組(Mini-Transaction)。MTR怎麼理解呢?如一條 insert 語句可能會使得頁分裂,新建葉子節點,原先頁的資料需要複製到新資料頁裡,然後將新記錄插入,再新增一個目錄項指向新建的頁子。這對應多條 redo 日誌,它們需要在原子性的 MTR 內完成

redo 日誌刷盤時機

MTR 產生的 redo 日誌先會被複制到一個 log buffer 裡(類似 buffer pool)。而同步到磁碟的時機如下:

  • 當 log buffer 的總容量達到 50% ,則重新整理日誌到磁碟
  • 事務提交時,也需要將同步到磁碟
  • 後臺執行緒,每一秒同步一次
  • 關閉 mysql 服務
  • 做 checkpoit 的時候
    • redo 的空間是有限的。若 redo 日誌對應的資料頁如果被同步到磁碟,則 redo 日誌也可被回收利用了。這回收的過程稱之為 checkpoint

2 undo 日誌

事務需要保證原子性,也是說事務中的操作要麼全部完成,要麼什麼也不做。如果事務執行到一半,出錯了怎麼辦-回滾。但是怎麼回滾呢,靠 undo 日誌。undo 日誌就是我們執行sql的逆操作

  • undo 日誌有兩個作用:提供回滾和多個行版本控制(MVCC)
  • 資料頁裡一行資料的格式如下,其中 roll_point 會指向一個undo 日誌
    image.png
  • undo 日誌一般會在事務提交時被刪除,但是如果 undo 日誌為 MVCC 服務 則暫時保留
  • 一個事務會產生多個 undo 日誌,mysql有專門的 undo 頁 儲存 undo 日誌。innodb 會為每一個事務單獨分配 undo 頁連結串列(最多分配 4 個連結串列)

事務ID 和 trx_id

  • mysql 會在記憶體中維護一個全域性變數,每當為某個事務分配 trx_id,則先分配再自增 1
  • 對於只讀事務,只有在它第一次建立的臨時表執行增刪改操作時,才會為事務分配 trx_id
  • 對於讀寫事務,只有它在執行增刪改操作時(包括臨時表),才會為事務分配 trx_id

roll_pointer

  • update、delete 語句對應的 undo 日誌都會帶 trx_id、roll_point 兩個屬性欄位。多條 sql 併發執行時 undo 日誌會根據 trx_id 順序用 roll_point 連成 undo 日誌版本鏈。MVCC 的原理則是靠 undo 日誌版本鏈實現的
    image.png

3 binlog日誌

  • binlog 檔案會隨服務的啟動建立一個新檔案
  • flush logs 可以手動重新整理日誌,生成一個新的 binlog 檔案
  • show master status 可以檢視 binlog 的狀態
  • reset master 可以清空 binlog 日誌檔案
  • mysqlbinlog 工具可以檢視 binlog 日誌的內容
  • 執行dml,mysql會自動記錄 binlog

binlog 格式

binlog有三種格式:Statement、Row以及Mixed。

  • Statement
    • 每一條增刪改資料的 sql 都會記錄在 binlog 中
    • 優點:不需要記錄每一行的變化,減少了binlog 日誌量,節約了IO,提高效能
    • 缺點:由於記錄的只是執行語句,為了這些語句能在 slave 上正確執行,因此還必須記錄每條語句在執行的時候的一些相關資訊。另外 mysql 的複製,像一些特定函式功能,slave 可與 master 要保持一致會有很多相關問題
  • Row
    • 5.1.5 版本的MySQL才開始支援 row level 的複製,它不記錄 sql 語句上下文相關資訊,僅儲存哪條記錄被修改
    • 優點:binlog 中可以不記錄執行的sql語句的上下文相關的資訊,僅需要記錄那一條記錄被修改成什麼了。所以rowlevel的日誌內容會非常清楚的記錄下每一行資料修改的細節
    • 缺點:所有的執行的語句當記錄到日誌中的時候,都將以每行記錄的修改來記錄,這樣可能會產生大量的日誌內容
  • Mixed
    • 在Mixed模式下,一般的語句修改使用statment格式儲存binlog,如一些函式,statement 格式無法完成主從複製的操作,則採用 row 格式儲存binlog

binlog 相關操作

  • 檢視binlog日誌檔案內容
[root@root log]# mysqlbinlog 'log.000001'
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!40019 SET @@session.max_insert_delayed_threads=0*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#181214 14:44:48 server id 1  end_log_pos 120 CRC32 0x79b6cd10 	Start: binlog v 4, server v 5.6.40-log created 181214 14:44:48 at startup
ROLLBACK/*!*/;
BINLOG '
YDIUXA8BAAAAdAAAAHgAAAAAAAQANS42LjQwLWxvZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAABgMhRcEzgNAAgAEgAEBAQEEgAAXAAEGggAAAAICAgCAAAACgoKGRkAARDN
tnk=
'/*!*/;
# at 120
#181214 14:45:20 server id 1  end_log_pos 199 CRC32 0x10dec193 	Query	thread_id=1	exec_time=0	error_code=0
SET TIMESTAMP=1544827520/*!*/;
SET @@session.pseudo_thread_id=1/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/;
SET @@session.sql_mode=1075838976/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C utf8 *//*!*/;
SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=8/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
BEGIN
/*!*/;
# at 199
#181214 14:45:20 server id 1  end_log_pos 303 CRC32 0x9ec5f952 	Query	thread_id=1	exec_time=0	error_code=0
use `test`/*!*/;
SET TIMESTAMP=1544827520/*!*/;
insert into t1 values('8','7')
/*!*/;
# at 303
#181214 14:45:20 server id 1  end_log_pos 334 CRC32 0xfd659542 	Xid = 10
COMMIT/*!*/;
# at 334
#181214 14:45:35 server id 1  end_log_pos 413 CRC32 0x43929486 	Query	thread_id=1	exec_time=0	error_code=0
SET TIMESTAMP=1544827535/*!*/;
BEGIN
/*!*/;
# at 413
#181214 14:45:35 server id 1  end_log_pos 517 CRC32 0x4f1284f2 	Query	thread_id=1	exec_time=0	error_code=0
SET TIMESTAMP=1544827535/*!*/;
insert into t1 values('9','7')
/*!*/;
# at 517
#181214 14:45:35 server id 1  end_log_pos 548 CRC32 0x67231f2b 	Xid = 20
COMMIT/*!*/;
# at 548
#181214 14:45:39 server id 1  end_log_pos 627 CRC32 0x82b39b3e 	Query	thread_id=1	exec_time=0	error_code=0
SET TIMESTAMP=1544827539/*!*/;
BEGIN
/*!*/;
# at 627
#181214 15:00:48 server id 1  end_log_pos 1646 CRC32 0x7e89c8dc 	Stop
DELIMITER ;
# End of log file
ROLLBACK /* added by mysqlbinlog */;
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;
  • 檢視binlog具體記錄
mysql> show binlog events in 'log.000001';
+------------+------+-------------+-----------+-------------+---------------------------------------------+
| Log_name   | Pos  | Event_type  | Server_id | End_log_pos | Info                                        |
+------------+------+-------------+-----------+-------------+---------------------------------------------+
| log.000001 |    4 | Format_desc |         1 |         120 | Server ver: 5.6.40-log, Binlog ver: 4       |
| log.000001 |  120 | Query       |         1 |         199 | BEGIN                                       |
| log.000001 |  199 | Query       |         1 |         303 | use `test`; insert into t1 values('8','7')  |
| log.000001 |  303 | Xid         |         1 |         334 | COMMIT /* xid=10 */                         |
| log.000001 |  334 | Query       |         1 |         413 | BEGIN                                       |
| log.000001 |  413 | Query       |         1 |         517 | use `test`; insert into t1 values('9','7')  |
| log.000001 |  517 | Xid         |         1 |         548 | COMMIT /* xid=20 */                         |
| log.000001 |  548 | Query       |         1 |         627 | BEGIN                                       |
| log.000001 |  627 | Query       |         1 |         732 | use `test`; insert into t1 values('10','7') |
| log.000001 |  732 | Xid         |         1 |         763 | COMMIT /* xid=30 */                         |
| log.000001 |  763 | Query       |         1 |         842 | BEGIN                                       |
| log.000001 |  842 | Query       |         1 |         947 | use `test`; insert into t1 values('11','7') |
| log.000001 |  947 | Xid         |         1 |         978 | COMMIT /* xid=40 */               
+------------+------+-------------+-----------+-------------+---------------------------------------------+
23 rows in set (0.00 sec)

redo log 和 binlog 區別

  • redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 層實現的,所有引擎都可以使用。redo log 是物理日誌,記錄的是“在某個資料頁上做了什麼修改”
  • binlog 是邏輯日誌,記錄的是這個語句的原始邏輯,比如“給 ID=2 這一行的 c 欄位加 1 ”
  • redo log 是迴圈寫的,空間固定會用完;binlog 是可以追加寫入的。“追加寫”是指 binlog 檔案寫到一定大小後會切換到下一個,並不會覆蓋以前的日誌

redo log 記錄事務是兩階段提交的

image.png

  • 如果 redo 不是兩階段提交;redo 先寫,binlog 後寫,會導致依賴 binlog 同步的從庫資料缺失。binlog 先寫,redo log 後寫,則會導致從庫多出未提交的髒修改。主從庫資料會不一致

redo log 、bin log 和崩潰恢復

redolog 中的事務如果經歷了二階段提交中的prepare階段,則會打上 prepare 標識,如果經歷commit階段,則會打上commit標識(此時redolog和binlog均已落盤)。崩潰恢復邏輯如下:

  • 按順序掃描 redo log,如果 redo log 中的事務既有 prepare 標識,又有 commit 標識,就直接提交(複製redo log disk中的資料頁到磁碟資料頁)
  • 如果 redo log 事務只有 prepare 標識,沒有 commit 標識,則說明當前事務在 commit 階段crash了,binlog 中當前事務是否完整未可知,此時拿著 redolog 中當前事務ID(redolog 和 binlog 中事務落盤的標識),去檢視 binlog 中是否存在此ID
    • 如果binlog中有當前事務ID,則提交事務(複製 redolog disk 中的資料頁到磁碟資料頁)
    • 如果binlog中沒有當前事務ID,則回滾事務(使用undolog來刪除 redolog 中的對應事務)

歡迎指正文中錯誤

參考文章

相關文章