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