數據庫篇:mysql日志類型之 redo、undo、binlog


前言

可以說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 中的對應事務)

歡迎指正文中錯誤

參考文章


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM