深入理解MySQL Binlog:從原理到實踐

binlog 介紹

記錄了所有的DDL和DML(除了數據查詢語句)語句,以事件形式記錄,還包含語句所執行的消耗的時間,MySQL的二進制日誌是事務安全型的。

作用:

  • 複製:MySQL Replication在Master端開啓binlog,Master把它的二進制日誌傳遞給slaves並回放來達到master-slave數據一致的目的
  • 數據恢復:通過mysqlbinlog工具恢復數據

存儲格式:二進制

存儲內容:

記錄更改數據的語句,不存 select 和 show 等查詢語句,查詢語句可以通過 access.log 查看

影響:

開啓binlog會使性能稍微變慢

管理 binlog

配置

開啓binlog

修改 /etc/my.cnf,在配置文件中加入 log-bin 配置,表示啓用binlog,如果沒有給定值,寫成 log-bin=,則默認名稱爲主機名。(注:名稱若帶有小數點,則只取第一個小數點前的部分作爲名稱) 

[mysqld]
log-bin=my-binlog-name

也可以通過 SET SQL_LOG_BIN=1 命令來啓用 binlog,通過 SET SQL_LOG_BIN=0 命令停用 binlog。啓用 binlog 之後須重啓MySQL才能生效。

ps:查看mysql進程指定的配置文件,如果沒有則是讀取的默認配置文件。查看msyql默認讀取的my.cnf目錄

mysql --help | grep 'my.cnf'

順序排前的優先

日誌文件名

binlog日誌文件有兩種:

  • 索引文件,後綴爲 .index,用於記錄所有有效的二進制文件
  • 二進制文件,後綴爲.0000*,記錄數據庫所有的DDL和DML事件

以下3種情況會創建一個新的日誌文件:

  • 重新啓動mysql
  • 刷新日誌
  • 日誌文件大小達到 max_binlog_size

日誌文件實際可能會比max_binlog_size大一點,因爲事務是整體寫入文件的,從不在文件之間拆分

日誌內容

# 查看binlog記錄的事件
show binlog events;

binlog是一個二進制文件集合,每個binlog文件以一個4字節的魔數開頭,接着是一組Events:

  • 魔數:0xfe62696e對應的是0xfebin;
  • Event:每個Event包含header和data兩個部分;header提供了Event的創建時間,哪個服務器等信息,data部分提供的是針對該Event的具體信息,如具體數據的修改;
  • 第一個Event用於描述binlog文件的格式版本,這個格式就是event寫入binlog文件的格式;
  • 其餘的Event按照第一個Event的格式版本寫入;
  • 最後一個Event用於說明下一個binlog文件;
  • binlog的索引文件是一個文本文件,其中內容爲當前的binlog文件列表

max_binlog_size 的最小值是4096字節,最大值和默認值是 1GB (1073741824字節)。事務被寫入到binlog的一個塊中,所以它不會在幾個二進制日誌之間被拆分。因此,如果你有很大的事務,爲了保證事務的完整性,不可能做切換日誌的動作,只能將該事務的日誌都記錄到當前日誌文件中,直到事務結束,你可能會看到binlog文件大於 max_binlog_size 的情況。

日誌格式

  • STATEMENT:基於SQL語句的複製(statement-based replication, SBR),MySQL 5.7.7 之前的默認格式
  • ROW:基於行的複製(row-based replication, RBR),默認使用BASE64編碼,需要解碼,MySQL 5.7.7 之後的默認格式
  • MIXED:混合模式複製(mixed-based replication, MBR),推薦使用

STATEMENT格式可能會導致主從不一致,

例如使用以下函數的語句無法被正確複製:
* LOAD_FILE()
* UUID()
* USER()
* FOUND_ROWS()
* SYSDATE() (除非啓動時啓用了 –sysdate-is-now 選項)

修改日誌格式,或set global binlog_format=ROW;

[mysqld]
binlog_format=ROW

Statement
每一條會修改數據的sql都會記錄在binlog中

優點:不需要記錄每一行的變化,減少了binlog日誌量,節約了IO, 提高了性能。
缺點:由於記錄的只是執行語句,爲了這些語句能在slave上正確運行,因此還必須記錄每條語句在執行的時候的一些相關信息,以保證所有語句能在slave得到和在master端執行的時候相同的結果。另外mysql的複製,像一些特定函數的功能,slave與master要保持一致會有很多相關問題。

Row
5.1.5版本的MySQL纔開始支持 row level 的複製,它不記錄sql語句上下文相關信息,僅保存哪條記錄被修改。

優點: binlog中可以不記錄執行的sql語句的上下文相關的信息,僅需要記錄那一條記錄被修改成什麼了。所以row的日誌內容會非常清楚的記錄下每一行數據修改的細節。而且不會出現某些特定情況下的存儲過程,或function,以及trigger的調用和觸發無法被正確複製的問題
缺點:所有的執行的語句當記錄到日誌中的時候,都將以每行記錄的修改來記錄,這樣可能會產生大量的日誌內容。

Mixed

從5.1.8版本開始,MySQL提供了Mixed格式,實際上就是Statement與Row的結合。
在Mixed模式下,一般的語句修改使用statment格式保存binlog,如一些函數,statement無法完成主從複製的操作,則採用row格式保存binlog,MySQL會根據執行的每一條具體的sql語句來區分對待記錄的日誌形式,也就是在Statement和Row之間選擇一種。

mysqlbinlog 工具

由於binlog文件內容是二進制格式,可以使用官方提供的 mysqlbinlog 工具來查看

mysqlbinlog [options] log-files

# 查看bin-log二進制文件(帶查詢條件)
mysqlbinlog -vv --base64-output=decode-rows /var/lib/mysql/binlog.000001 \
    --start-datetime="2023-06-01 00:00:00"  \
    --stop-datetime="2023-06-30 00:00:00"   \
    --start-position="4"    \
    --stop-position="500"   \
    -d database_name

執行後,輸出如下內容

截取一段來簡單說明下:

# at 1653
#230605 21:04:05 server id 1  end_log_pos 1792 CRC32 0x287f5a5d 	Query	thread_id=8	exec_time=183	error_code=0
SET TIMESTAMP=1685970245/*!*/;
insert into test2 values(1, 'ryan', 1)
/*!*/;

上面輸出包括信息:

  • position: 位於文件中的位置,即第一行的(# at 1653),說明該事件記錄從文件第1653個字節開始
  • timestamp: 事件發生的時間戳,即第二行的(#230605 21:04:05 )
  • server id: 服務器標識(1)
  • end_log_pos 表示下一個事件開始的位置(即當前事件的結束位置+1)
  • thread_id: 執行該事件的線程id (thread_id=8)
  • CRC32 0x287f5a5d:CRC32是一種用於校驗數據完整性的校驗和算法。CRC32算法可以將數據轉換爲一個32位的校驗和值,用於檢測數據是否被篡改或損壞
  • exec_time: 事件執行的花費時間
  • error_code: 錯誤碼,0意味着沒有發生錯誤
  • type:事件類型Query

實踐

一般恢復邏輯,每天備份,根據具體的時間點找到對應的binlog,使用mysqlbinlog導出,然後剔除掉誤操作的SQL,在新的庫上進行重放,然後在替換線上的表。

日誌產生的性能影響:

由於日誌的記錄帶來的直接性能損耗就是數據庫系統中最爲昂貴的IO資源。

MySQL的日誌主要包括錯誤日誌(ErrorLog),二進制日誌(Binlog),查詢日誌(QueryLog),慢查詢日誌(SlowQueryLog)等。

在默認情況下,系統僅僅打開錯誤日誌,關閉了其他所有日誌,以達到儘可能減少IO損耗提高系統性能的目的。
但是在一般稍微重要一點的實際應用場景中,都至少需要打開二進制日誌,因爲這是MySQL很多存儲引擎進行增量備份的基礎,也是MySQL實現複製的基本條件。
有時候爲了進一步的mysql性能優化,定位執行較慢的SQL語句,很多系統也會打開慢查詢日誌來記錄執行時間超過特定數值(由我們自行設置)的SQL語句。

一般情況下,在生產系統中很少有系統會打開查詢日誌。因爲查詢日誌打開之後會將MySQL中執行的每一條Query都記錄到日誌中,會該系統帶來比較大的IO負擔,而帶來的實際效益卻並不是非常大。一般只有在開發測試環境中,爲了定位某些功能具體使用了哪些SQL語句的時候,纔會在短時間段內打開該日誌來做相應的分析。
所以,在MySQL系統中,會對性能產生影響的MySQL日誌(不包括各存儲引擎自己的日誌)主要就是Binlog了。

官方文檔:

https://dev.mysql.com/doc/refman/5.7/en/binary-log.html

主從同步配置

附:常用binlog操作命令

# 一、配置信息
# 是否啓用binlog日誌
show variables like 'log_bin';

# 查看詳細的日誌配置信息
show global variables like '%log%';

# mysql數據存儲目錄
show variables like '%dir%';

# 查看binlog的目錄
show global variables like "%log_bin%";

# 查看當前服務器使用的binlog文件及大小
show binary logs;

# 查看主服務器使用的binlog文件及大小


# 二、管理binlog
# 查看所有binlog的日誌列表
show master logs;

# 查看最後一個binlog日誌的編號名稱及最後一個時間結束的位置pos
show master status;

# 刷新binlog,會生成一個新編號的binlog日誌文件
flush log;

# 清空所有binlog日誌(慎用)
reset master;

# 設置binlog文件保存事件,過期刪除,單位天
set global expire_log_days=3; 

# 刪除指定日期前的日誌索引中binlog日誌文件
purge master logs before '2019-03-09 14:00:00';

# 刪除指定日誌文件
purge master logs to 'master.000003';

# 刪除slave的中繼日誌
reset slave;

# 三、事件查詢命令
# 查看 binlog 內容
show binlog events;

# 查看具體一個binlog文件的內容 (in 後面爲binlog的文件名)
show binlog events in 'master.000001';

# IN 'log_name' :指定要查詢的binlog文件名(不指定就是第一個binlog文件)
# FROM pos :指定從哪個pos起始點開始查起(不指定就是從整個文件首個pos點開始算)
# LIMIT [offset,] :偏移量(不指定就是0)
# row_count :查詢總條數(不指定就是所有行)
show binlog events [IN 'log_name'] [FROM pos] [LIMIT [offset,] row_count];

my.cnf 配置

binlog_format = MIXED              //binlog日誌格式
log_bin =目錄/mysql-bin.log       //binlog日誌名
expire_logs_days = 7                 //binlog過期清理時間
max_binlog_size = 100m              //binlog每個日誌文件大小

binlog-do-db=需要備份的數據庫名,如果備份多個數據庫,重複設置這個選項即可
binlog-ignore-db=不需要備份的數據庫苦命,如果備份多個數據庫,重複設置這個選項即可

“binlog_cache_size”:在事務過程中容納二進制日誌SQL語句的緩存大小。二進制日誌緩存是服務器支持事務存儲引擎並且服務器啓用了二進制日誌(—log-bin選項)的前提下爲每個客戶端分配的內存,注意,是每個Client都可以分配設置大小的binlogcache空間。如果讀者朋友的系統中經常會出現多語句事務的華,可以嘗試增加該值的大小,以獲得更有的性能。當然,我們可以通過MySQL的以下兩個狀態變量來判斷當前的binlog_cache_size的狀況:Binlog_cache_use和Binlog_cache_disk_use。

“max_binlog_cache_size”:和”binlog_cache_size”相對應,但是所代表的是binlog能夠使用的最大cache內存大小。當我們執行多語句事務的時候,max_binlog_cache_size如果不夠大的話,系統可能會報出“Multi-statementtransactionrequiredmorethan’max_binlog_cache_size’bytesofstorage”的錯誤。

“max_binlog_size”:Binlog日誌最大值,一般來說設置爲512M或者1G,但不能超過1G。該大小並不能非常嚴格控制Binlog大小,尤其是當到達Binlog比較靠近尾部而又遇到一個較大事務的時候,系統爲了保證事務的完整性,不可能做切換日誌的動作,只能將該事務的所有SQL都記錄進入當前日誌,直到該事務結束。這一點和Oracle的Redo日誌有點不一樣,因爲Oracle的Redo日誌所記錄的是數據文件的物理位置的變化,而且裏面同時記錄了Redo和Undo相關的信息,所以同一個事務是否在一個日誌中對Oracle來說並不關鍵。而MySQL在Binlog中所記錄的是數據庫邏輯變化信息,MySQL稱之爲Event,實際上就是帶來數據庫變化的DML之類的Query語句。

“sync_binlog”:這個參數是對於MySQL系統來說是至關重要的,他不僅影響到Binlog對MySQL所帶來的性能損耗,而且還影響到MySQL中數據的完整性。對於“sync_binlog”參數的各種設置的說明如下:

sync_binlog=0,當事務提交之後,MySQL不做fsync之類的磁盤同步指令刷新binlog_cache中的信息到磁盤,而讓Filesystem自行決定什麼時候來做同步,或者cache滿了之後才同步到磁盤。

sync_binlog=n,當每進行n次事務提交之後,MySQL將進行一次fsync之類的磁盤同步指令來將binlog_cache中的數據強制寫入磁盤。

在MySQL中系統默認的設置是sync_binlog=0,也就是不做任何強制性的磁盤刷新指令,這時候的性能是最好的,但是風險也是最大的。因爲一旦系統Crash,在binlog_cache中的所有binlog信息都會被丟失。而當設置爲“1”的時候,是最安全但是性能損耗最大的設置。因爲當設置爲1的時候,即使系統Crash,也最多丟失binlog_cache中未完成的一個事務,對實際數據沒有任何實質性影響。從以往經驗和相關測試來看,對於高併發事務的系統來說,“sync_binlog”設置爲0和設置爲1的系統寫入性能差距可能高達5倍甚至更多。

另:
MySQL的複製(Replication),實際上就是通過將Master端的Binlog通過利用IO線程通過網絡複製到Slave端,然後再通過SQL線程解析Binlog中的日誌再應用到數據庫中來實現的。所以,Binlog量的大小對IO線程以及Msater和Slave端之間的網絡都會產生直接的影響。

MySQL中Binlog的產生量是沒辦法改變的,只要我們的Query改變了數據庫中的數據,那麼就必須將該Query所對應的Event記錄到Binlog中。那我們是不是就沒有辦法優化複製了呢?當然不是,在MySQL複製環境中,實際上是是有8個參數可以讓我們控制需要複製或者需要忽略而不進行復制的DB或者Table的,分別爲:

Binlog_Do_DB:設定哪些數據庫(Schema)需要記錄Binlog;

Binlog_Ignore_DB:設定哪些數據庫(Schema)不要記錄Binlog;

Replicate_Do_DB:設定需要複製的數據庫(Schema),多個DB用逗號(“,”)分隔;

Replicate_Ignore_DB:設定可以忽略的數據庫(Schema);

Replicate_Do_Table:設定需要複製的Table;

Replicate_Ignore_Table:設定可以忽略的Table;

Replicate_Wild_Do_Table:功能同Replicate_Do_Table,但可以帶通配符來進行設置;

Replicate_Wild_Ignore_Table:功能同Replicate_Ignore_Table,可帶通配符設置;

通過上面這八個參數,我們就可以非常方便按照實際需求,控制從Master端到Slave端的Binlog量儘可能的少,從而減小Master端到Slave端的網絡流量,減少IO線程的IO量,還能減少SQL線程的解析與應用SQL的數量,最終達到改善Slave上的數據延時問題。

實際上,上面這八個參數中的前面兩個是設置在Master端的,而後面六個參數則是設置在Slave端的。雖然前面兩個參數和後面六個參數在功能上並沒有非常直接的關係,但是對於優化MySQL的Replication來說都可以啓到相似的功能。當然也有一定的區別,其主要區別如下:

如果在Master端設置前面兩個參數,不僅僅會讓Master端的Binlog記錄所帶來的IO量減少,還會讓Master端的IO線程就可以減少Binlog的讀取量,傳遞給Slave端的IO線程的Binlog量自然就會較少。這樣做的好處是可以減少網絡IO,減少Slave端IO線程的IO量,減少Slave端的SQL線程的工作量,從而最大幅度的優化複製性能。當然,在Master端設置也存在一定的弊端,因爲MySQL的判斷是否需要複製某個Event不是根據產生該Event的Query所更改的數據

所在的DB,而是根據執行Query時刻所在的默認Schema,也就是我們登錄時候指定的DB或者運行“USEDATABASE”中所指定的DB。只有當前默認DB和配置中所設定的DB完全吻合的時候IO線程纔會將該Event讀取給Slave的IO線程。所以如果在系統中出現在默認DB和設定需要複製的DB不一樣的情況下改變了需要複製的DB中某個Table的數據的時候,該Event是不會被複制到Slave中去的,這樣就會造成Slave端的數據和Master的數據不一致的情況出現。同樣,如果在默認Schema下更改了不需要複製的Schema中的數據,則會被複制到Slave端,當Slave端並沒有該Schema的時候,則會造成複製出錯而停止。

而如果是在Slave端設置後面的六個參數,在性能優化方面可能比在Master端要稍微遜色一點,因爲不管是需要還是不需要複製的Event都被會被IO線程讀取到Slave端,這樣不僅僅增加了網絡IO量,也給Slave端的IO線程增加了RelayLog的寫入量。但是仍然可以減少Slave的SQL線程在Slave端的日誌應用量。雖然性能方面稍有遜色,但是在Slave端設置複製過濾機制,可以保證不會出現因爲默認Schema的問題而造成Slave和Master數據不一致或者複製出錯的問題。

 

原文:https://www.ryanzoe.top/%e6%95%b0%e6%8d%ae%e5%ba%93/mysql/mysql-%e8%af%af%e6%93%8d%e4%bd%9c-%e6%95%b0%e6%8d%ae%e6%81%a2%e5%a4%8d/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章