mysql binlog介紹以及通過binlog實現資料恢復
mysql的二進位制日誌binlog可以說是mysql最重要的日誌,它記錄了所有資料更新sql,以事件形式記錄,還包含語句所執行的消耗的時間,mysql的二進位制日誌是事務安全型的。binlog日誌主要用於mysql主從複製和資料恢復。
原文地址:程式碼匯個人部落格http://www.codehui.net/info/66.html
簡單瞭解binlog
binlog是一個二進位制格式的檔案,用於記錄使用者對資料庫更新的sql語句資訊,但是不包括select和show這類操作,因為這類操作對資料本身並沒有修改。然後,若操作本身並沒有導致資料庫發生變化,那麼該操作也會寫入二進位制日誌。
預設情況下,binlog日誌是二進位制格式的,不能使用檢視文字工具的命令(比如,cat,vi等)檢視,而使用mysqlbinlog 解析檢視。
一般來說開啟二進位制日誌大概會有1%的效能損耗,其他介紹可以檢視官網文件介紹
binlog作用
binlog日誌有兩個最重要的使用場景
1、mysql主從複製
MySQL Replication在Master端開啟binlog,Master把它的二進位制日誌傳遞給slaves來達到master-slave資料一致的目的。
可以參考:《mysql 主從複製 基於binlog 簡單實踐》
2、資料恢復
通過使用mysqlbinlog程式處理二進位制日誌檔案,來使恢復資料。
binlog日誌包括兩類檔案
1、二進位制日誌索引檔案(檔名字尾為.index)用於記錄所有的二進位制檔案
2、二進位制日誌檔案(檔名字尾為.00000*)記錄資料庫所有的DDL和DML(除了資料查詢語句select)語句事件。在mysql 主從複製 中有見到。
開啟binlog日誌
1、修改配置檔案
可以通過如下命令檢視mysql讀取的配置檔案,順序排前的優先
root@ba586179fe4b:/# mysql --help|grep 'my.cnf' order of preference, my.cnf, $MYSQL_TCP_PORT, /etc/my.cnf /etc/mysql/my.cnf /usr/local/mysql/etc/my.cnf ~/.my.cnf
編輯配置檔案,然後重啟mysql
root@ba586179fe4b:/# vi /etc/my.cnf [mysqld] # 開啟二進位制日誌功能,mysql-bin 是日誌的基本名或字首名 log-bin=mysql-bin
2、登入資料庫mysql -u root -p123456
檢視binlog日誌是否開啟,log_bin為ON表示開啟binlog日誌
mysql> show variables like 'log_%'; +---------------------------------+-------+ | Variable_name| Value | +---------------------------------+-------+ | log_bin| ON| | log_bin_trust_function_creators | OFF| | log_error|| | log_output| FILE| | log_queries_not_using_indexes| OFF| | log_slave_updates| OFF| | log_slow_queries| OFF| | log_warnings| 1| +---------------------------------+-------+ 8 rows in set (0.00 sec)
binlog日誌常用操作命令
1、檢視所有binlog日誌列表
mysql> show master logs; +------------------+-----------+ | Log_name| File_size | +------------------+-----------+ | mysql-bin.000001 |8184 | | mysql-bin.000002 |107 | +------------------+-----------+ 2 rows in set (0.00 sec)
2、檢視master狀態,即最後(最新)一個binlog日誌的編號名稱,及其最後一個操作事件pos結束點(Position)值
mysql> show master status; +------------------+----------+--------------+------------------+ | File| Position | Binlog_Do_DB | Binlog_Ignore_DB | +------------------+----------+--------------+------------------+ | mysql-bin.000002 |107 ||| +------------------+----------+--------------+------------------+ 1 row in set (0.00 sec)
3、重新整理log日誌,自此刻開始產生一個新編號的binlog日誌檔案
mysql> flush logs; Query OK, 0 rows affected (0.01 sec)
4、重置(清空)所有binlog日誌
mysql> reset master; Query OK, 0 rows affected (0.01 sec)
檢視binlog日誌內容,常用有兩種方式
1、使用mysqlbinlog自帶檢視命令
注: binlog是二進位制檔案,普通檔案檢視器cat more vi等都無法開啟,必須使用自帶的 mysqlbinlog 命令檢視,binlog日誌與資料庫檔案在同目錄中(我的環境配置安裝是選擇在/usr/local/mysql/data中)
在MySQL5.5以下版本使用mysqlbinlog命令時如果報錯,就加上"--no-defaults"選項
mysql資料存放在/var/lib/mysql
目錄,通過mysqlbinlog開啟日誌檔案
/usr/local/mysql/bin/mysqlbinlog /var/lib/mysql/mysql-bin.000001
擷取最後一次執行的日誌片段
# at 1778 #1902216:58:51 server id 1end_log_pos 1899Querythread_id=1 exec_time=0 error_code=0 SET TIMESTAMP=1550732331/*!*/; INSERT INTO `proxy` (`id`, `name`) VALUES ('6', 'code') /*!*/; # at 1899 #1902216:58:51 server id 1end_log_pos 1926Xid = 45
-
主要引數解釋:
- server id 1 : 資料庫主機的服務號;
- end_log_pos 1899: sql結束時的pos節點
- thread_id=1: 執行緒號
2、使用show binlog events命令檢視
上面這種辦法讀取出binlog日誌的全文內容較多,不容易分辨檢視pos點資訊,這裡介紹一種更為方便的查詢命令:
mysql> show binlog events [IN 'log_name'] [FROM pos] [LIMIT [offset,] row_count];
選項解析:
IN 'log_name'指定要查詢的binlog檔名(不指定就是第一個binlog檔案)
FROM pos指定從哪個pos起始點開始查起(不指定就是從整個檔案首個pos點開始算)
LIMIT [offset,] 偏移量(不指定就是0)
row_count查詢總條數(不指定就是所有行)
mysql> show master logs; +------------------+-----------+ | Log_name| File_size | +------------------+-----------+ | mysql-bin.000001 |326 | +------------------+-----------+ 1 row in set (0.00 sec) mysql> show binlog events in 'mysql-bin.000001'\G; *************************** 1. row *************************** Log_name: mysql-bin.000001 Pos: 4 Event_type: Format_desc Server_id: 1 End_log_pos: 107 Info: Server ver: 5.5.62-log, Binlog ver: 4 *************************** 2. row *************************** Log_name: mysql-bin.000001 Pos: 107 Event_type: Query Server_id: 1 End_log_pos: 178 Info: BEGIN *************************** 3. row *************************** Log_name: mysql-bin.000001 Pos: 178 Event_type: Query Server_id: 1 End_log_pos: 299 Info: use `codehui`; INSERT INTO `proxy` (`id`, `name`) VALUES ('6', 'code') *************************** 4. row *************************** Log_name: mysql-bin.000001 Pos: 299 Event_type: Xid Server_id: 1 End_log_pos: 326 Info: COMMIT /* xid=61 */ 4 rows in set (0.00 sec) ERROR: No query specified mysql>
上面這條語句可以將指定的binlog日誌檔案,分成有效事件行的方式返回,並可使用limit指定pos點的起始偏移,查詢條數。
- 查詢第一個(最早)的binlog日誌:
mysql> show binlog events\G;
- 指定查詢 mysql-bin.000021 這個檔案:
mysql> show binlog events in 'mysql-bin.000021'\G;
- 指定查詢 mysql-bin.000021 這個檔案,從pos點:8224開始查起:
mysql> show binlog events in 'mysql-bin.000021' from 8224\G;
- 指定查詢 mysql-bin.000021 這個檔案,從pos點:8224開始查起,查詢10條
mysql> show binlog events in 'mysql-bin.000021' from 8224 limit 10\G;
- 指定查詢 mysql-bin.000021 這個檔案,從pos點:8224開始查起,偏移2行,查詢10條
mysql> show binlog events in 'mysql-bin.000021' from 8224 limit 2,10\G;
通過binlog資料恢復
這個資料測試比較麻煩,我們先模擬個場景
codehui資料庫會在每天凌晨1點使用計劃任務進行一次備份,這裡先手動執行一下備份任務。然後就有了資料庫截止今天凌晨1點的資料庫備份檔案。早上9點和中午12點資料庫都執行了增刪改操作,然後下午18點直接刪掉了codehui資料庫,場景大概就是這樣,下面進行測試資料的恢復。
先在codehui資料庫插入測試資料
mysql> use codehui; Database changed mysql> CREATE TABLE `test` ( -> `id` int(11) NOT NULL AUTO_INCREMENT,\ -> `name` varchar(255) CHARACTER SET utf8 DEFAULT NULL, -> PRIMARY KEY (`id`) -> ) ENGINE=InnoDB DEFAULT CHARSET=latin1; Query OK, 0 rows affected (0.10 sec) mysql> desc test; +-------+--------------+------+-----+---------+----------------+ | Field | Type| Null | Key | Default | Extra| +-------+--------------+------+-----+---------+----------------+ | id| int(11)| NO| PRI | NULL| auto_increment | | name| varchar(255) | YES|| NULL|| +-------+--------------+------+-----+---------+----------------+ 2 rows in set (0.00 sec) mysql> INSERT INTO `test` (`id`, `name`) VALUES ('1', 'code'); Query OK, 1 row affected (0.00 sec) mysql> INSERT INTO `test` (`id`, `name`) VALUES ('2', 'php'); Query OK, 1 row affected (0.04 sec) mysql> select * from test; +----+------+ | id | name | +----+------+ |1 | code | |2 | php| +----+------+ 2 rows in set (0.00 sec) mysql>
1、先備份一下資料庫
備份資料庫方法mysqldump,詳細請參見mysql 資料備份
root@ba586179fe4b:/# /usr/local/mysql/bin/mysqldump -uroot -p123456 -B -F -R -x --master-data=2 codehui|gzip > /opt/backup/codehui.bak.sql.gz root@ba586179fe4b:/# ls /opt/backup codehui.bak.sql.gz
mysqldump備份方法引數說明:
-B:指定資料庫
-F:重新整理日誌
-R:備份儲存過程等
-x:鎖表
--master-data:在備份語句裡新增CHANGE MASTER語句以及binlog檔案及位置點資訊
由於上面在全備份的時候使用了-F選項,那麼當資料備份操作剛開始的時候系統就會自動重新整理log,這樣就會自動產生一個新的binlog日誌,這個新的binlog日誌就會用來記錄備份之後的資料庫"增刪改"操作
mysql> show master status; +------------------+----------+--------------+------------------+ | File| Position | Binlog_Do_DB | Binlog_Ignore_DB | +------------------+----------+--------------+------------------+ | mysql-bin.000003 |107 ||| +------------------+----------+--------------+------------------+ 1 row in set (0.00 sec)
也就是說, mysql-bin.000003 是用來記錄凌晨1點之後對資料庫的所有"增刪改"操作。
2、早上9點對資料庫進行"增"操作,新插入3條資料
mysql> INSERT INTO `test` (`id`, `name`) VALUES ('3', 'java'),('4','golang'),('5','shell'); Query OK, 3 rows affected (0.02 sec) Records: 3Duplicates: 0Warnings: 0 mysql> select * from test; +----+--------+ | id | name| +----+--------+ |1 | code| |2 | php| |3 | java| |4 | golang | |5 | shell| +----+--------+ 5 rows in set (0.00 sec)
3、中午12點對資料庫進行"改"操作,修改1條資料
mysql> UPDATE `test` SET `name`='mysql' WHERE `id`='1'; Query OK, 1 row affected (0.02 sec) Rows matched: 1Changed: 1Warnings: 0 mysql> select * from test; +----+--------+ | id | name| +----+--------+ |1 | mysql| |2 | php| |3 | java| |4 | golang | |5 | shell| +----+--------+ 5 rows in set (0.00 sec)
4、下午18點,某程式設計師因心情不爽準備跑路,刪掉了資料庫codehui
mysql> drop database codehui; Query OK, 3 rows affected (0.14 sec)
5、此刻先別慌,他忘記了我們還有大招,就是binlog日誌。
先仔細檢視最後一個binlog日誌,並記錄下關鍵的pos點,到底是哪個pos點的操作導致了資料庫的破壞(通常在最後幾步);
我們先備份一下最後一個binlog日誌
root@ba586179fe4b:/# cp -v /var/lib/mysql/mysql-bin.000003 /opt/backup/ '/var/lib/mysql/mysql-bin.000003' -> '/opt/backup/mysql-bin.000003'
此時執行一次重新整理日誌索引操作,重新開始新的binlog日誌記錄檔案。理論說 mysql-bin.000003 這個檔案不會再有後續寫入了(便於我們分析原因及查詢pos點),以後所有資料庫操作都會寫入到下一個日誌檔案;
mysql> flush logs; Query OK, 0 rows affected (0.03 sec) mysql> show master status; +------------------+----------+--------------+------------------+ | File| Position | Binlog_Do_DB | Binlog_Ignore_DB | +------------------+----------+--------------+------------------+ | mysql-bin.000004 |107 ||| +------------------+----------+--------------+------------------+ 1 row in set (0.00 sec)
6、讀取日誌 分析問題,讀取日誌方法上面已經說到,這裡使用第二種
mysql> show binlog events in 'mysql-bin.000003'; +------------------+------+-------------+-----------+-------------+----------------------------------------------------------------------------------------------------+ | Log_name| Pos| Event_type| Server_id | End_log_pos | Info| +------------------+------+-------------+-----------+-------------+----------------------------------------------------------------------------------------------------+ | mysql-bin.000003 |4 | Format_desc |1 |107 | Server ver: 5.5.62-log, Binlog ver: 4| | mysql-bin.000003 |107 | Query|1 |178 | BEGIN| | mysql-bin.000003 |178 | Query|1 |327 | use `codehui`; INSERT INTO `test` (`id`, `name`) VALUES ('3', 'java'),('4','golang'),('5','shell') | | mysql-bin.000003 |327 | Xid|1 |354 | COMMIT /* xid=184 */| | mysql-bin.000003 |354 | Query|1 |425 | BEGIN| | mysql-bin.000003 |425 | Query|1 |544 | use `codehui`; INSERT INTO `proxy` (`id`, `name`) VALUES ('1', 'my')| | mysql-bin.000003 |544 | Xid|1 |571 | COMMIT /* xid=188 */| | mysql-bin.000003 |571 | Query|1 |642 | BEGIN| | mysql-bin.000003 |642 | Query|1 |784 | use `codehui`; UPDATE `proxy` SET `name`='mysql' WHERE (`id`='1') AND (`name`='my') LIMIT 1| | mysql-bin.000003 |784 | Xid|1 |811 | COMMIT /* xid=190 */| | mysql-bin.000003 |811 | Query|1 |882 | BEGIN| | mysql-bin.000003 |882 | Query|1 |995 | use `codehui`; UPDATE `test` SET `name`='mysql' WHERE `id`='1'| | mysql-bin.000003 |995 | Xid|1 |1022 | COMMIT /* xid=192 */| | mysql-bin.000003 | 1022 | Query|1 |1109 | drop database codehui| | mysql-bin.000003 | 1109 | Rotate|1 |1152 | mysql-bin.000004;pos=4| +------------------+------+-------------+-----------+-------------+----------------------------------------------------------------------------------------------------+ 15 rows in set (0.00 sec)
到這裡是不很興奮,看到了備份之後執行的所有的"增刪改"記錄。
通過分析,造成資料庫破壞的pos點區間是介於 1022--1109 之間(這是按照日誌區間的pos節點算的),只要恢復到1109前就可。
7、恢復凌晨1點的備份資料,也就是剛才手動備份的資料
root@ba586179fe4b:/# cd /opt/backup/ root@ba586179fe4b:/opt/backup# ls codehui.bak.sql.gzmysql-bin.000003 root@ba586179fe4b:/opt/backup# gzip -d codehui.bak.sql.gz root@ba586179fe4b:/opt/backup# mysql -uroot -p123456 -v < codehui.bak.sql
這樣就恢復了截至當日凌晨(1:00)前的備份資料都恢復了,之後的資料通過binlog日誌mysql-bin.000003進行恢復。
mysql> show databases; +--------------------+ | Database| +--------------------+ | information_schema | | codehui| | mysql| | performance_schema | +--------------------+ 4 rows in set (0.00 sec) mysql> use codehui; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql> show tables; +-------------------+ | Tables_in_codehui | +-------------------+ | demo| | proxy| | test| +-------------------+ 3 rows in set (0.00 sec) mysql> select * from test; +----+------+ | id | name | +----+------+ |1 | code | |2 | php| +----+------+ 2 rows in set (0.00 sec)
8、從binlog日誌恢復資料
恢復命令的語法格式:
mysqlbinlog mysql-bin.0000xx | mysql -u使用者名稱 -p密碼 資料庫名
-
常用引數選項解釋:
- --start-position=875 起始pos點
- --stop-position=954 結束pos點
- --start-datetime="2016-9-25 22:01:08" 起始時間點
- --stop-datetime="2019-9-25 22:09:46" 結束時間點
- --database=codehui 指定只恢復codehui資料庫(一臺主機上往往有多個數據庫,只限本地log日誌)
-
不常用選項:
- -u --user=name 連線到遠端主機的使用者名稱
- -p --password[=name] 連線到遠端主機的密碼
- -h --host=name 從遠端主機上獲取binlog日誌
- --read-from-remote-server 從某個MySQL伺服器上讀取binlog日誌
小結:實際是將讀出的binlog日誌內容,通過管道符傳遞給mysql命令。這些命令、檔案儘量寫成絕對路徑;
A、完全恢復(需要手動編輯mysql-bin.000003,將那條drop語句剔除掉)
溫馨提示:在恢復全備資料之前必須將該binlog檔案移出,否則恢復過程中,會繼續寫入語句到binlog,最終導致增量恢復資料部分變得比較混亂!
root@ba586179fe4b:/opt/backup# mysqlbinlog /opt/backup/mysql-bin.000003 > /opt/backup/000003.sql root@ba586179fe4b:/opt/backup# vi /opt/backup/000003.sql #刪除裡面的drop語句 # 刪掉drop語句前後的# at 到 /*!*/之間的內容 root@ba586179fe4b:/opt/backup# mysql -uroot -p123456 -v < /opt/backup/000003.sql
檢視資料,已經恢復了
mysql> use codehui; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql> select * from test; +----+--------+ | id | name| +----+--------+ |1 | mysql| |2 | php| |3 | java| |4 | golang | |5 | shell| +----+--------+ 5 rows in set (0.00 sec) # 然後刪除codehui資料庫,測試第二種方法 mysql> drop database codehui; Query OK, 3 rows affected (0.06 sec) # 重新匯入凌晨1點的備份資料, root@ba586179fe4b:/opt/backup# mysql -uroot -p123456 -v < codehui.bak.sql
B、指定pos結束點恢復(部分恢復):
--stop-position=571 pos結束節點(按照事務區間算,是571)
-
注意:
- 此pos結束節點介於"test"表原始資料與更新"name='mysql'"之前的資料,這樣就可以恢復到更改"name='mysql'"之前的資料了。
操作如下
root@ba586179fe4b:/opt/backup# mysqlbinlog --stop-position=571 --database=codehui /var/lib/mysql/mysql-bin.000003 | mysql -uroot -p123456 -v codehui mysql> use codehui; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql> select * from test; +----+--------+ | id | name| +----+--------+ |1 | code| |2 | php| |3 | java| |4 | golang | |5 | shell| +----+--------+ 5 rows in set (0.00 sec)
C、指定pos點區間恢復(部分恢復):
更新 "name='mysql'" 這條資料,日誌區間是Pos[882] --> End_log_pos[995],按事務區間是:Pos[811] --> End_log_pos[1022]
單獨恢復 "name='mysql'" 這步操作,可這樣:
按照binlog日誌區間單獨恢復:
root@ba586179fe4b:/opt/backup# mysqlbinlog --start-position=882 --stop-position=995 --database=codehui /var/lib/mysql/mysql-bin.000003 | mysql -uroot -p123456 -v codehui
按照事務區間單獨恢復
root@ba586179fe4b:/opt/backup# mysqlbinlog --start-position=811 --stop-position=1022 --database=codehui /var/lib/mysql/mysql-bin.000003 | mysql -uroot -p123456 -v codehui
如果要恢復區間內的多條日誌,按事務區間恢復就可以。
mysql> use codehui; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql> select * from test; +----+--------+ | id | name| +----+--------+ |1 | mysql| |2 | php| |3 | java| |4 | golang | |5 | shell| +----+--------+ 5 rows in set (0.00 sec)
檢視資料庫恢復了"name='mysql'",這樣就恢復了刪除前的資料狀態了。
D、也可指定時間區間恢復(部分恢復):除了用pos點的辦法進行恢復,也可以通過指定時間區間進行恢復,按時間恢復需要用mysqlbinlog命令讀取binlog日誌內容,找時間節點。
# 起始時間點 --start-datetime="YYYY-MM-DD H:I:S" # 結束時間點 --stop-datetime ="YYYY-MM-DD H:I:S" # 用法舉例 mysqlbinlog --start-position=811 --start-datetime="YYYY-MM-DD H:I:S" --stop-datetime="YYYY-MM-DD H:I:S" --database=codehui /var/lib/mysql/mysql-bin.000003 | mysql -uroot -p123456 -v codehui