前言
可以說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 日志
- 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 日志版本鏈實現的
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 記錄事務是兩階段提交的
- 如果 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 中的對應事務)