誤操作后快速恢復數據 binlog 解析為反向 SQL
1.前言
本文將介紹使用 reverse_sql 工具將 GreatSQL 數據庫的 binlog 解析為反向 SQL 語句。模擬誤操作后,恢復數據。該工具可以幫助客戶在發生事故時快速恢復數據,避免進一步的損失。使用 reverse_sql
工具非常簡單,客戶只需要指定肇事時間和表名即可。該工具會根據指定的時間點,在數據庫中查找并還原該表在該時間點之前的數據狀態。這樣客戶就能輕松地實現數據恢復,防止因意外操作或其他問題導致的數據丟失。
2.reverse_sql 工具簡介
reverse_sql 是一個用于解析和轉換二進制日志(binlog)的工具。它可以將二進制日志文件中記錄的數據庫更改操作(如插入、更新、刪除)轉換為反向的 SQL 語句,以便進行數據恢復。其運行模式需二進制日志設置為 ROW 格式。
下載地址:
https://github.com/hcymysql/reverse_sql
3.工具特點
該工具的主要功能和特點包括(針對 GreatSQL):
1、解析二進制日志:reverse_sql
能夠解析 GreatSQL 的二進制日志文件,并還原出其中的 SQL 語句。
2、生成可讀的 SQL:生成原始 SQL 和反向 SQL。
3、支持過濾和篩選:可以根據時間范圍、表、DML操作等條件來過濾出具體的誤操作 SQL 語句。
4、支持多線程并發解析 binlog 事件。
注意:reverse_sql
只是將二進制日志還原為 SQL 語句,而不會執行這些 SQL 語句來修改數據庫。
4.使用前檢查
4.1 GreatSQL 環境檢查
首先需要確認二進制日志設置是 ROW 格式以及 row_image 是 FULL。
工具運行時,首先會進行GreatSQL的環境檢測(if binlog_format != 'ROW' and binlog_row_image != 'FULL'
),如果不同時滿足這兩個條件,程序直接退出。
greatsql> SHOW VARIABLES LIKE '%binlog%';
+------------------------------------------------+----------------------+
| Variable_name | Value |
+------------------------------------------------+----------------------+
| binlog_cache_size | 32768 |
| binlog_checksum | CRC32 |
| binlog_ddl_skip_rewrite | OFF |
| binlog_direct_non_transactional_updates | OFF |
| binlog_encryption | OFF |
| binlog_error_action | ABORT_SERVER |
| binlog_expire_logs_auto_purge | ON |
| binlog_expire_logs_seconds | 2592000 |
| binlog_format | ROW |
| binlog_group_commit_sync_delay | 0 |
| binlog_group_commit_sync_no_delay_count | 0 |
| binlog_gtid_simple_recovery | ON |
| binlog_max_flush_queue_time | 0 |
| binlog_order_commits | ON |
| binlog_rotate_encryption_master_key_at_startup | OFF |
| binlog_row_event_max_size | 8192 |
| binlog_row_image | FULL |
| binlog_row_metadata | MINIMAL |
| binlog_row_value_options | |
| binlog_rows_query_log_events | OFF |
| binlog_skip_flush_commands | OFF |
| binlog_space_limit | 0 |
| binlog_stmt_cache_size | 32768 |
| binlog_transaction_compression | OFF |
| binlog_transaction_compression_level_zstd | 3 |
| binlog_transaction_dependency_history_size | 25000 |
| binlog_transaction_dependency_tracking | WRITESET |
| have_backup_safe_binlog_info | YES |
| innodb_api_enable_binlog | OFF |
| log_statements_unsafe_for_binlog | ON |
| max_binlog_cache_size | 4294967296 |
| max_binlog_size | 1073741824 |
| max_binlog_stmt_cache_size | 18446744073709547520 |
| rpl_read_binlog_speed_limit | 0 |
| sync_binlog | 1 |
+------------------------------------------------+----------------------+
35 rows in set (0.01 sec)
4.2 參數binlog_format解析
GreatSQL 支持三種二進制日志格式:
-
STATEMENT (基于語句的復制 - SBR)
-
記錄實際執行的 SQL 語句
-
優點:日志文件較小,記錄的是語句而非數據變更
-
缺點:某些非確定性函數(如 NOW(), UUID(), RAND())可能導致主從數據不一致
-
-
ROW (基于行的復制 - RBR) GreatSQL 默認使用 ROW 作為二進制日志格式。
-
記錄每行數據的變化情況
-
優點:最安全的復制方式,能準確復制數據變更
-
缺點:日志文件較大,特別是批量操作時
-
-
MIXED (混合模式)
- 默認使用 STATEMENT 格式,但在某些情況下自動切換到 ROW 格式
4.3 參數binlog_row_image解析
binlog_row_image 是 GreatSQL 中控制二進制日志記錄的參數,它決定了在使用基于行的復制時,二進制日志中的行鏡像如何被記錄。
參數選項
binlog_row_image 參數有三個可選值:
- FULL:記錄每一行的變更,包括所有列的前后鏡像。–生產環境建議設置為 FULL 最佳。
- MINIMAL:binlog日志的前鏡像只記錄唯一識別列(唯一索引列、主鍵列),后鏡像只記錄修改列。只記錄必要的列,即在更新操作中只記錄變更的列和用于識別行的最小列集。
- NOBLOB:類似于 FULL,但不包括 BLOB 和 TEXT 類型的列,除非它們是必要的。
使用場景
- 當設置為 FULL 時,GreatSQL 記錄所有列的變更,這可以確保數據的完整性,但可能會導致二進制日志的大小增加。
- 設置為 MINIMAL 可以減少日志的大小,因為它只記錄變更的列和必要的列,這對于減少磁盤 I/O 和網絡傳輸是有益的。
- NOBLOB 選項適用于那些不希望記錄大型 BLOB 或 TEXT 數據的場景,但仍然需要記錄其他類型列的變更。
5.工具與數據庫用戶賦權
5.1 工具賦予執行權限
在服務器上解壓后,授權:
$ ll rev*
-rwxr-xr-x 1 root root 50780824 Apr 28 14:23 reverse_sql_mysql8
$ chmod 755 reverse_sql_mysql8
5.2 數據庫用戶賦權(最小化用戶權限)
greatsql> CREATE USER 'test'@'%' identified BY 'test';
Query OK, 0 rows affected (0.15 sec)greatsql> GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO `test`@`%`;
Query OK, 0 rows affected (0.01 sec)greatsql> GRANT SELECT ON `test`.* TO `test`@`%`;
Query OK, 0 rows affected (0.01 sec)greatsql> SHOW grants FOR test;
+------------------------------------------------------------------+
| Grants FOR test@% |
+------------------------------------------------------------------+
| GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO `test`@`%` |
| GRANT SELECT ON `test`.* TO `test`@`%` |
+------------------------------------------------------------------+
2 rows in set (0.00 sec)
5.3 查看使用幫助信息
$ ./reverse_sql_mysql8 --help
usage: reverse_sql_mysql8 [-h] [-ot ONLY_TABLES [ONLY_TABLES ...]] [-op ONLY_OPERATION] -H MYSQL_HOST -P MYSQL_PORT -u MYSQL_USER -p MYSQL_PASSWD -d MYSQL_DATABASE [-c MYSQL_CHARSET] --binlog-file BINLOG_FILE[--binlog-pos BINLOG_POS] --start-time ST --end-time ET [--max-workers MAX_WORKERS] [--print] [--replace] [-v]Binlog數據恢復,生成反向SQL語句。options:-h, --help show this help message and exit-ot ONLY_TABLES [ONLY_TABLES ...], --only-tables ONLY_TABLES [ONLY_TABLES ...]設置要恢復的表,多張表用,逗號分隔-op ONLY_OPERATION, --only-operation ONLY_OPERATION設置誤操作時的命令(insert/update/delete)-H MYSQL_HOST, --mysql-host MYSQL_HOSTMySQL主機名-P MYSQL_PORT, --mysql-port MYSQL_PORTMySQL端口號-u MYSQL_USER, --mysql-user MYSQL_USERMySQL用戶名-p MYSQL_PASSWD, --mysql-passwd MYSQL_PASSWDMySQL密碼-d MYSQL_DATABASE, --mysql-database MYSQL_DATABASEMySQL數據庫名-c MYSQL_CHARSET, --mysql-charset MYSQL_CHARSETMySQL字符集,默認utf8--binlog-file BINLOG_FILEBinlog文件--binlog-pos BINLOG_POSBinlog位置,默認4--start-time ST 起始時間--end-time ET 結束時間--max-workers MAX_WORKERS線程數,默認4(并發越高,鎖的開銷就越大,適當調整并發數)--print 將解析后的SQL輸出到終端--replace 將update轉換為replace操作-v, --version show program's version number and exitExample usage:shell> ./reverse_sql -ot table1 -op delete -H 192.168.198.239 -P 3336 -u admin -p hechunyang -d hcy \--binlog-file mysql-bin.000124 --start-time "2023-07-06 10:00:00" --end-time "2023-07-06 22:00:00"
6.案例演示
6.1 創建測試數據,模擬誤更新
greatsql> CREATE TABLE t1 ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50), age INT, email VARCHAR(100), gender ENUM('Male', 'Female', 'Other'), salary DECIMAL(10,2), join_date DATE, is_active BOOLEAN );
Query OK, 0 rows affected (0.04 sec)greatsql> INSERT INTO t1 (name, age, email, gender, salary, join_date, is_active) VALUES ('張三', 28, 'zhangsan@example.com', 'Male', 8500.00, '2020-05-15', TRUE), ('李四', 32, 'lisi@example.com', 'Male', 9200.50, '2019-08-22', TRUE), ('王五', 25, 'wangwu@example.com', 'Male', 7800.00, '2021-03-10', TRUE), ('趙六', 29, 'zhaoliu@example.com', 'Female', 8800.75, '2020-11-05', TRUE), ('肖七', 35, 'xiaoqi@example.com', 'Female', 10500.00, '2018-06-18', FALSE), ('孫八', 27, 'sunba@example.com', 'Male', 8100.00, '2021-01-30', TRUE), ('周九', 31, 'zhoujiu@example.com', 'Other', 9500.25, '2019-09-12', TRUE), ('吳十', 24, 'wushi@example.com', 'Female', 7600.50, '2022-02-14', TRUE), ('鄭十一', 30, 'zhengshiyi@example.com', 'Male', 8900.00, '2020-07-25', FALSE), ('王十二', 33, 'wangshier@example.com', 'Female', 10000.00, '2019-04-08', TRUE);
Query OK, 10 rows affected (0.01 sec)
Records: 10 Duplicates: 0 Warnings: 0greatsql> SELECT * FROM t1;
+----+-----------+------+------------------------+--------+----------+------------+-----------+
| id | name | age | email | gender | salary | join_date | is_active |
+----+-----------+------+------------------------+--------+----------+------------+-----------+
| 1 | 張三 | 28 | zhangsan@example.com | Male | 8500.00 | 2020-05-15 | 1 |
| 2 | 李四 | 32 | lisi@example.com | Male | 9200.50 | 2019-08-22 | 1 |
| 3 | 王五 | 25 | wangwu@example.com | Male | 7800.00 | 2021-03-10 | 1 |
| 4 | 趙六 | 29 | zhaoliu@example.com | Female | 8800.75 | 2020-11-05 | 1 |
| 5 | 肖七 | 35 | xiaoqi@example.com | Female | 10500.00 | 2018-06-18 | 0 |
| 6 | 孫八 | 27 | sunba@example.com | Male | 8100.00 | 2021-01-30 | 1 |
| 7 | 周九 | 31 | zhoujiu@example.com | Other | 9500.25 | 2019-09-12 | 1 |
| 8 | 吳十 | 24 | wushi@example.com | Female | 7600.50 | 2022-02-14 | 1 |
| 9 | 鄭十一 | 30 | zhengshiyi@example.com | Male | 8900.00 | 2020-07-25 | 0 |
| 10 | 王十二 | 33 | wangshier@example.com | Female | 10000.00 | 2019-04-08 | 1 |
+----+-----------+------+------------------------+--------+----------+------------+-----------+
10 rows in set (0.00 sec)greatsql> UPDATE t1 SET salary = 9800.00, is_active = TRUE WHERE id = 5;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0greatsql> SELECT * FROM t1;
+----+-----------+------+------------------------+--------+----------+------------+-----------+
| id | name | age | email | gender | salary | join_date | is_active |
+----+-----------+------+------------------------+--------+----------+------------+-----------+
| 1 | 張三 | 28 | zhangsan@example.com | Male | 8500.00 | 2020-05-15 | 1 |
| 2 | 李四 | 32 | lisi@example.com | Male | 9200.50 | 2019-08-22 | 1 |
| 3 | 王五 | 25 | wangwu@example.com | Male | 7800.00 | 2021-03-10 | 1 |
| 4 | 趙六 | 29 | zhaoliu@example.com | Female | 8800.75 | 2020-11-05 | 1 |
| 5 | 肖七 | 35 | xiaoqi@example.com | Female | 9800.00 | 2018-06-18 | 1 |
| 6 | 孫八 | 27 | sunba@example.com | Male | 8100.00 | 2021-01-30 | 1 |
| 7 | 周九 | 31 | zhoujiu@example.com | Other | 9500.25 | 2019-09-12 | 1 |
| 8 | 吳十 | 24 | wushi@example.com | Female | 7600.50 | 2022-02-14 | 1 |
| 9 | 鄭十一 | 30 | zhengshiyi@example.com | Male | 8900.00 | 2020-07-25 | 0 |
| 10 | 王十二 | 33 | wangshier@example.com | Female | 10000.00 | 2019-04-08 | 1 |
+----+-----------+------+------------------------+--------+----------+------------+-----------+
10 rows in set (0.00 sec)
6.2 解析binlog
查看 binlog 當前信息
greatsql> SHOW MASTER STATUS \G
*************************** 1. row ***************************File: binlog.000002Position: 2918Binlog_Do_DB: Binlog_Ignore_DB:
Executed_Gtid_Set: 2a3248f7-e762-11ef-ae09-00163e11ac96:1,
3837053e-e762-11ef-ade8-00163e2cc6be:1-4008,
615fadb3-234d-11f0-ab29-00163e2cc6be:1-5,
aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaa1:1-1067166
1 row in set (0.00 sec)
執行解析命令
./reverse_sql_mysql8 -u 'test' -p 'test' -H 172.17.136.70 -P 3301 -d test_111 -ot t1 --binlog-file /greatsql/dbdata/3301/log/binlog.000009 --start-time "2025-05-22 16:30:00" --end-time "2025-05-22 16:40:00"Processing binlogevents: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 50.32event/s]
Processing binlogevents: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 2/2 [00:00<00:00, 111.72event/s]
Processing binlogevents: 0event [00:00, ?event/s]
Processing binlogevents: 0event [00:00, ?event/s]
當出現誤操作時,只需指定誤操作的時間段,其對應的 binlog 文件(通常可以通過 show master status 得到當前的 binlog 文件名)以及剛才誤操作的表,和具體的 DML 命令,比如 update 或者 delete。【不支持一次解析多個 binlog】
6.3 查看解析文件
$ ll test*
-rw-r--r-- 1 root root 5892 Apr 28 15:47 test_111_t1_recover_2025-04-28_15:47:17.sql
$
$ cat test_111_t1_recover_2025-04-28_15:47:17.sql
-- SQL執行時間:2025-04-28 15:41:32
-- 原生sql:-- INSERT INTO `test_111`.`t1`(`id`,`name`,`age`,`email`,`gender`,`salary`,`join_date`,`is_active`) VALUES (1,'張三',28,'zhangsan@example.com','Male',8500.00,'2020-05-15',1);
-- 回滾sql:DELETE FROM `test_111`.`t1` WHERE `id`=1 AND `name`='張三' AND `age`=28 AND `email`='zhangsan@example.com' AND `gender`='Male' AND `salary`=8500.00 AND `join_date`='2020-05-15' AND `is_active`=1;
-- ----------------------------------------------------------
-- SQL執行時間:2025-04-28 15:41:32
-- 原生sql:-- INSERT INTO `test_111`.`t1`(`id`,`name`,`age`,`email`,`gender`,`salary`,`join_date`,`is_active`) VALUES (2,'李四',32,'lisi@example.com','Male',9200.50,'2019-08-22',1);
-- 回滾sql:DELETE FROM `test_111`.`t1` WHERE `id`=2 AND `name`='李四' AND `age`=32 AND `email`='lisi@example.com' AND `gender`='Male' AND `salary`=9200.50 AND `join_date`='2019-08-22' AND `is_active`=1;
-- ----------------------------------------------------------
-- SQL執行時間:2025-04-28 15:41:32
-- 原生sql:-- INSERT INTO `test_111`.`t1`(`id`,`name`,`age`,`email`,`gender`,`salary`,`join_date`,`is_active`) VALUES (3,'王五',25,'wangwu@example.com','Male',7800.00,'2021-03-10',1);
-- 回滾sql:DELETE FROM `test_111`.`t1` WHERE `id`=3 AND `name`='王五' AND `age`=25 AND `email`='wangwu@example.com' AND `gender`='Male' AND `salary`=7800.00 AND `join_date`='2021-03-10' AND `is_active`=1;
-- ----------------------------------------------------------
-- SQL執行時間:2025-04-28 15:41:32
-- 原生sql:-- INSERT INTO `test_111`.`t1`(`id`,`name`,`age`,`email`,`gender`,`salary`,`join_date`,`is_active`) VALUES (4,'趙六',29,'zhaoliu@example.com','Female',8800.75,'2020-11-05',1);
-- 回滾sql:DELETE FROM `test_111`.`t1` WHERE `id`=4 AND `name`='趙六' AND `age`=29 AND `email`='zhaoliu@example.com' AND `gender`='Female' AND `salary`=8800.75 AND `join_date`='2020-11-05' AND `is_active`=1;
-- ----------------------------------------------------------
-- SQL執行時間:2025-04-28 15:41:32
-- 原生sql:-- INSERT INTO `test_111`.`t1`(`id`,`name`,`age`,`email`,`gender`,`salary`,`join_date`,`is_active`) VALUES (5,'肖七',35,'xiaoqi@example.com','Female',10500.00,'2018-06-18',0);
-- 回滾sql:DELETE FROM `test_111`.`t1` WHERE `id`=5 AND `name`='肖七' AND `age`=35 AND `email`='xiaoqi@example.com' AND `gender`='Female' AND `salary`=10500.00 AND `join_date`='2018-06-18' AND `is_active`=0;
-- ----------------------------------------------------------
-- SQL執行時間:2025-04-28 15:41:32
-- 原生sql:-- INSERT INTO `test_111`.`t1`(`id`,`name`,`age`,`email`,`gender`,`salary`,`join_date`,`is_active`) VALUES (6,'孫八',27,'sunba@example.com','Male',8100.00,'2021-01-30',1);
-- 回滾sql:DELETE FROM `test_111`.`t1` WHERE `id`=6 AND `name`='孫八' AND `age`=27 AND `email`='sunba@example.com' AND `gender`='Male' AND `salary`=8100.00 AND `join_date`='2021-01-30' AND `is_active`=1;
-- ----------------------------------------------------------
-- SQL執行時間:2025-04-28 15:41:32
-- 原生sql:-- INSERT INTO `test_111`.`t1`(`id`,`name`,`age`,`email`,`gender`,`salary`,`join_date`,`is_active`) VALUES (7,'周九',31,'zhoujiu@example.com','Other',9500.25,'2019-09-12',1);
-- 回滾sql:DELETE FROM `test_111`.`t1` WHERE `id`=7 AND `name`='周九' AND `age`=31 AND `email`='zhoujiu@example.com' AND `gender`='Other' AND `salary`=9500.25 AND `join_date`='2019-09-12' AND `is_active`=1;
-- ----------------------------------------------------------
-- SQL執行時間:2025-04-28 15:41:32
-- 原生sql:-- INSERT INTO `test_111`.`t1`(`id`,`name`,`age`,`email`,`gender`,`salary`,`join_date`,`is_active`) VALUES (8,'吳十',24,'wushi@example.com','Female',7600.50,'2022-02-14',1);
-- 回滾sql:DELETE FROM `test_111`.`t1` WHERE `id`=8 AND `name`='吳十' AND `age`=24 AND `email`='wushi@example.com' AND `gender`='Female' AND `salary`=7600.50 AND `join_date`='2022-02-14' AND `is_active`=1;
-- ----------------------------------------------------------
-- SQL執行時間:2025-04-28 15:41:32
-- 原生sql:-- INSERT INTO `test_111`.`t1`(`id`,`name`,`age`,`email`,`gender`,`salary`,`join_date`,`is_active`) VALUES (9,'鄭十一',30,'zhengshiyi@example.com','Male',8900.00,'2020-07-25',0);
-- 回滾sql:DELETE FROM `test_111`.`t1` WHERE `id`=9 AND `name`='鄭十一' AND `age`=30 AND `email`='zhengshiyi@example.com' AND `gender`='Male' AND `salary`=8900.00 AND `join_date`='2020-07-25' AND `is_active`=0;
-- ----------------------------------------------------------
-- SQL執行時間:2025-04-28 15:41:32
-- 原生sql:-- INSERT INTO `test_111`.`t1`(`id`,`name`,`age`,`email`,`gender`,`salary`,`join_date`,`is_active`) VALUES (10,'王十二',33,'wangshier@example.com','Female',10000.00,'2019-04-08',1);
-- 回滾sql:DELETE FROM `test_111`.`t1` WHERE `id`=10 AND `name`='王十二' AND `age`=33 AND `email`='wangshier@example.com' AND `gender`='Female' AND `salary`=10000.00 AND `join_date`='2019-04-08' AND `is_active`=1;
-- ----------------------------------------------------------
-- SQL執行時間:2025-04-28 15:41:57
-- 原生sql:-- UPDATE `test_111`.`t1` SET `id`=5,`name`='肖七',`age`=35,`email`='xiaoqi@example.com',`gender`='Female',`salary`=9800.00,`join_date`='2018-06-18',`is_active`=1 WHERE `id`=5 AND `name`='肖七' AND `age`=35 AND `email`='xiaoqi@example.com' AND `gender`='Female' AND `salary`=10500.00 AND `join_date`='2018-06-18' AND `is_active`=0;
-- 回滾sql:UPDATE `test_111`.`t1` SET `id`=5,`name`='肖七',`age`=35,`email`='xiaoqi@example.com',`gender`='Female',`salary`=10500.00,`join_date`='2018-06-18',`is_active`=0 WHERE `id`=5 AND `name`='肖七' AND `age`=35 AND `email`='xiaoqi@example.com' AND `gender`='Female' AND `salary`=9800.00 AND `join_date`='2018-06-18' AND `is_active`=1;
-- ----------------------------------------------------------
$
工具運行后,會在當前目錄下生成一個 {db}_{table}_recover.sql 文件,保存著原生 SQL(原生 SQL 會加注釋) 和反向 SQL,如果想將結果輸出到前臺終端,可以指定 --print 選項。
$ ./reverse_sql_mysql8 -u 'test' -p 'test' -H 172.17.136.70 -P 3301 -d test_111 -ot t1 --binlog-file /greatsql/dbdata/3301/log/binlog.000008 --start-time "2025-05-21 11:10:00" --end-time "2025-05-21 11:18:00" --print
Processing binlogevents: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 4/4 [00:00<00:00, 258.00event/s]
Processing binlogevents: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 4/4 [00:00<00:00, 292.57event/s]
Processing binlogevents: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 5/5 [00:00<00:00, 277.56event/s]
Processing binlogevents: 0event [00:00, ?event/s]
-- SQL執行時間:2025-05-21 11:14:01
-- 原生sql:-- INSERT INTO `test_111`.`t1`(`id`,`name`,`age`,`email`,`gender`,`salary`,`join_date`,`is_active`) VALUES (1,'張三',28,'zhangsan@example.com','Male',8500.00,'2020-05-15',1);
-- 回滾sql:DELETE FROM `test_111`.`t1` WHERE `id`=1 AND `name`='張三' AND `age`=28 AND `email`='zhangsan@example.com' AND `gender`='Male' AND `salary`=8500.00 AND `join_date`='2020-05-15' AND `is_active`=1;
-- ------------------------------------------------------------ SQL執行時間:2025-05-21 11:14:01
-- 原生sql:-- INSERT INTO `test_111`.`t1`(`id`,`name`,`age`,`email`,`gender`,`salary`,`join_date`,`is_active`) VALUES (2,'李四',32,'lisi@example.com','Male',9200.50,'2019-08-22',1);
-- 回滾sql:DELETE FROM `test_111`.`t1` WHERE `id`=2 AND `name`='李四' AND `age`=32 AND `email`='lisi@example.com' AND `gender`='Male' AND `salary`=9200.50 AND `join_date`='2019-08-22' AND `is_active`=1;
-- ----------------------------------------------------------
如果你想把 update 操作轉換為 replace,指定 --replace 選項即可,同時會在當前目錄下生成一個{db}_{table}_recover_replace.sql 文件。
$ ./reverse_sql_mysql8 -u 'test' -p 'test' -H 172.17.136.70 -P 3301 -d test_111 -ot t1 --binlog-file /greatsql/dbdata/3301/log/binlog.000008 --start-time "2025-05-21 11:10:00" --end-time "2025-05-21 11:18:00" --replace
Processing binlogevents: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 4/4 [00:00<00:00, 83.25event/s]
Processing binlogevents: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 4/4 [00:00<00:00, 112.54event/s]
Processing binlogevents: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 5/5 [00:00<00:00, 172.61event/s]
Processing binlogevents: 0event [00:00, ?event/s]
$ ll
-rwxr-xr-x 1 root root 50780824 May 21 10:28 reverse_sql_mysql8
-rw-r--r-- 1 root root 646 May 21 11:19 test_111_t1_recover_2025-05-21_11:19:28_replace.sql
-rw-r--r-- 1 root root 5892 May 21 11:19 test_111_t1_recover_2025-05-21_11:19:28.sql
$ cat test_111_t1_recover_2025-05-21_11:19:28_replace.sql
-- SQL執行時間:2025-05-21 11:14:36
-- 原生sql:-- UPDATE `test_111`.`t1` SET `id`=5,`name`='錢七',`age`=35,`email`='xiaoqi@example.com',`gender`='Female',`salary`=9800.00,`join_date`='2018-06-18',`is_active`=1 WHERE `id`=5 AND `name`='錢七' AND `age`=35 AND `email`='xiaoqi@example.com' AND `gender`='Female' AND `salary`=10500.00 AND `join_date`='2018-06-18' AND `is_active`=0;
-- 回滾sql:REPLACE INTO `test_111`.`t1` (`id`,`name`,`age`,`email`,`gender`,`salary`,`join_date`,`is_active`) VALUES (5,'錢七',35,'xiaoqi@example.com','Female',10500.00,'2018-06-18',0);
-- ----------------------------------------------------------
但注意,如果字段值中包含換行符,則原生 SQL 后幾行不會被注釋,需要手動處理。
6.4 拿到反向SQL恢復數據
在{db}_{table}_recover.sql 文件中找到剛才誤操作的 DML 語句,然后在 GreatSQL 數據庫中執行逆向工程后的 SQL 以恢復數據。
-- SQL執行時間:2025-04-28 15:41:57
-- 原生sql:-- UPDATE `test_111`.`t1` SET `id`=5,`name`='肖七',`age`=35,`email`='xiaoqi@example.com',`gender`='Female',`salary`=9800.00,`join_date`='2018-06-18',`is_active`=1 WHERE `id`=5 AND `name`='肖七' AND `age`=35 AND `email`='xiaoqi@example.com' AND `gender`='Female' AND `salary`=10500.00 AND `join_date`='2018-06-18' AND `is_active`=0;
-- 回滾sql:UPDATE `test_111`.`t1` SET `id`=5,`name`='肖七',`age`=35,`email`='xiaoqi@example.com',`gender`='Female',`salary`=10500.00,`join_date`='2018-06-18',`is_active`=0 WHERE `id`=5 AND `name`='肖七' AND `age`=35 AND `email`='xiaoqi@example.com' AND `gender`='Female' AND `salary`=9800.00 AND `join_date`='2018-06-18' AND `is_active`=1;
greatsql> UPDATE `test_111`.`t1` SET `id`=5,`name`='肖七',`age`=35,`email`='xiaoqi@example.com',`gender`='Female',`salary`=10500.00,`join_date`='2018-06-18',`is_active`=0 WHERE `id`=5 AND `name`='肖七' AND `age`=35 AND `email`='xiaoqi@example.com' AND `gender`='Female' AND `salary`=9800.00 AND `join_date`='2018-06-18' AND `is_active`=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0greatsql> SELECT * FROM t1;
+----+-----------+------+------------------------+--------+----------+------------+-----------+
| id | name | age | email | gender | salary | join_date | is_active |
+----+-----------+------+------------------------+--------+----------+------------+-----------+
| 1 | 張三 | 28 | zhangsan@example.com | Male | 8500.00 | 2020-05-15 | 1 |
| 2 | 李四 | 32 | lisi@example.com | Male | 9200.50 | 2019-08-22 | 1 |
| 3 | 王五 | 25 | wangwu@example.com | Male | 7800.00 | 2021-03-10 | 1 |
| 4 | 趙六 | 29 | zhaoliu@example.com | Female | 8800.75 | 2020-11-05 | 1 |
| 5 | 肖七 | 35 | xiaoqi@example.com | Female | 10500.00 | 2018-06-18 | 0 |
| 6 | 孫八 | 27 | sunba@example.com | Male | 8100.00 | 2021-01-30 | 1 |
| 7 | 周九 | 31 | zhoujiu@example.com | Other | 9500.25 | 2019-09-12 | 1 |
| 8 | 吳十 | 24 | wushi@example.com | Female | 7600.50 | 2022-02-14 | 1 |
| 9 | 鄭十一 | 30 | zhengshiyi@example.com | Male | 8900.00 | 2020-07-25 | 0 |
| 10 | 王十二 | 33 | wangshier@example.com | Female | 10000.00 | 2019-04-08 | 1 |
+----+-----------+------+------------------------+--------+----------+------------+-----------+
10 rows in set (0.00 sec)
可以看到這條誤更新的數據已經恢復到最初狀態。
如果 {db}_{table}_recover.sql 文件的內容過多,也可以通過 awk 命令進行分割,以便更容易進行排查。
$ awk '/^-- SQL執行時間/{filename = "output" ++count ".sql"; print > filename; next} {print > filename}' test_111_t1_recover_2025-04-28_15:47:17.sql
$ ll output*
-rw-r--r-- 1 root root 524 Apr 28 16:22 output10.sql
-rw-r--r-- 1 root root 802 Apr 28 16:22 output11.sql
-rw-r--r-- 1 root root 508 Apr 28 16:22 output1.sql
-rw-r--r-- 1 root root 500 Apr 28 16:22 output2.sql
-rw-r--r-- 1 root root 504 Apr 28 16:22 output3.sql
-rw-r--r-- 1 root root 510 Apr 28 16:22 output4.sql
-rw-r--r-- 1 root root 510 Apr 28 16:22 output5.sql
-rw-r--r-- 1 root root 502 Apr 28 16:22 output6.sql
-rw-r--r-- 1 root root 508 Apr 28 16:22 output7.sql
-rw-r--r-- 1 root root 506 Apr 28 16:22 output8.sql
-rw-r--r-- 1 root root 518 Apr 28 16:22 output9.sql
$ cat output1.sql
-- SQL執行時間:2025-04-28 15:41:32
-- 原生sql:-- INSERT INTO `test_111`.`t1`(`id`,`name`,`age`,`email`,`gender`,`salary`,`join_date`,`is_active`) VALUES (1,'張三',28,'zhangsan@example.com','Male',8500.00,'2020-05-15',1);
-- 回滾sql:DELETE FROM `test_111`.`t1` WHERE `id`=1 AND `name`='張三' AND `age`=28 AND `email`='zhangsan@example.com' AND `gender`='Male' AND `salary`=8500.00 AND `join_date`='2020-05-15' AND `is_active`=1;
-- ----------------------------------------------------------
$
不支持 drop 和 truncate 操作,因為這兩個操作屬于物理性刪除,需要通過歷史備份進行恢復。
可能存在的問題:
- 數據庫密碼含特殊字符:需要加單引號圈起來;
- 數據庫字段中如有換行符或者其他特殊字符,則原生sql顯示的時候后面可能不會注釋,導致回滾 sql 錯亂。因此需要我們將原生 SQL 用正則替換,即“-- 原生sql:”和“-- 回滾sql:”之間的部分(-- 原生sql:.*?-- 回滾sql:)
7.reverse_sql與binlog2sql主要區別
7.1 reverse_sql工具解析
./reverse_sql_mysql8 --help
usage: reverse_sql_mysql8 [-h] [-ot ONLY_TABLES [ONLY_TABLES ...]] [-op ONLY_OPERATION] -H MYSQL_HOST -P MYSQL_PORT -u MYSQL_USER -p MYSQL_PASSWD -d MYSQL_DATABASE [-c MYSQL_CHARSET] --binlog-file BINLOG_FILE[--binlog-pos BINLOG_POS] --start-time ST --end-time ET [--max-workers MAX_WORKERS] [--print] [--replace] [-v]Binlog數據恢復,生成反向SQL語句。options:-h, --help show this help message and exit-ot ONLY_TABLES [ONLY_TABLES ...], --only-tables ONLY_TABLES [ONLY_TABLES ...]設置要恢復的表,多張表用,逗號分隔-op ONLY_OPERATION, --only-operation ONLY_OPERATION設置誤操作時的命令(insert/update/delete)-H MYSQL_HOST, --mysql-host MYSQL_HOSTMySQL主機名-P MYSQL_PORT, --mysql-port MYSQL_PORTMySQL端口號-u MYSQL_USER, --mysql-user MYSQL_USERMySQL用戶名-p MYSQL_PASSWD, --mysql-passwd MYSQL_PASSWDMySQL密碼-d MYSQL_DATABASE, --mysql-database MYSQL_DATABASEMySQL數據庫名-c MYSQL_CHARSET, --mysql-charset MYSQL_CHARSETMySQL字符集,默認utf8--binlog-file BINLOG_FILEBinlog文件--binlog-pos BINLOG_POSBinlog位置,默認4--start-time ST 起始時間--end-time ET 結束時間--max-workers MAX_WORKERS線程數,默認4(并發越高,鎖的開銷就越大,適當調整并發數)--print 將解析后的SQL輸出到終端--replace 將update轉換為replace操作-v, --version show program's version number and exitExample usage:shell> ./reverse_sql -ot table1 -op delete -H 192.168.198.239 -P 3336 -u admin -p hechunyang -d hcy \--binlog-file mysql-bin.000124 --start-time "2023-07-06 10:00:00" --end-time "2023-07-06 22:00:00"
7.2 binlog2sql 工具解析
python binlog2sql.py --help
解析模式:
--stop-never 持續解析binlog。可選。默認False,同步至執行命令時最新的binlog位置。
-K, --no-primary-key 對INSERT語句去除主鍵。可選。默認False
-B, --flashback 生成回滾SQL,可解析大文件,不受內存限制。可選。默認False。與stop-never或no-primary-key不能同時添加。
--back-interval -B模式下,每打印一千行回滾SQL,加一句SLEEP多少秒,如不想加SLEEP,請設為0。可選。默認1.0。
解析范圍控制:
--start-file 起始解析文件,只需文件名,無需全路徑 。必須。
--start-position/--start-pos 起始解析位置。可選。默認為start-file的起始位置。
--stop-file/--end-file 終止解析文件。可選。默認為start-file同一個文件。若解析模式為stop-never,此選項失效。
--stop-position/--end-pos 終止解析位置。可選。默認為stop-file的最末位置;若解析模式為stop-never,此選項失效。
--start-datetime 起始解析時間,格式'%Y-%m-%d %H:%M:%S'。可選。默認不過濾。
--stop-datetime 終止解析時間,格式'%Y-%m-%d %H:%M:%S'。可選。默認不過濾。
對象過濾:
-d, --databases 只解析目標db的sql,多個庫用空格隔開,如-d db1 db2。可選。默認為空。
-t, --tables 只解析目標table的sql,多張表用空格隔開,如-t tbl1 tbl2。可選。默認為空。
--only-dml 只解析dml,忽略ddl。可選。默認False。
--sql-type 只解析指定類型,支持INSERT, UPDATE, DELETE。多個類型用空格隔開,如--sql-type INSERT DELETE。可選。默認為增刪改都解析。用了此參數但沒填任何類型,則三者都不解析。
7.3 兩個工具的區別
核心功能對比
特性 | reverse_sql | binlog2sql |
---|---|---|
生成方向 | 專注反向SQL (UNDO) | 默認正向SQL,需加 -B 參數生成反向SQL |
輸出格式 | 直接生成可執行的回滾SQL | 可選原始SQL/回滾SQL/帶注釋的SQL |
過濾條件 | 基礎過濾(時間/表名) | 更靈活(GTID/position/庫名等) |
大文件處理 | 未明確說明 | 明確支持大文件(-B 模式不受內存限制) |
時間范圍 | 必須指定 --start-time 和 --end-time | 可選(支持按時間或位置過濾) |
并發控制 | 支持線程數調節(–max-workers) | 無并發控制選項 |
斷點續傳 | 依賴binlog位置(–binlog-pos) | 支持更靈活的起止位置(–start-pos/–end-pos) |
表級過濾 | 支持(-ot 多表逗號分隔) | 支持(-t 多表空格分隔) |
操作類型過濾 | 必須指定(-op 僅限一種操作) | 靈活過濾(–sql-type 可多選或留空) |
DDL忽略 | 不支持 | 支持(–only-dml 忽略DDL) |
輸出控制 | 終端打印(–print)或直接執行 | 默認打印,支持回滾SLEEP間隔(–back-interval) |
主鍵處理 | 無相關選項 | 支持去除主鍵(-K 模式) |
UPDATE轉換 | 支持轉REPLACE(–replace) | 無此功能 |
- 數據恢復方向
reverse_sql
:專門用于生成反向 SQL(UNDO SQL),主要用于數據回滾/修復場景binlog2sql
:默認生成正向 SQL,也可通過參數生成反向 SQL
- 實現方式
reverse_sql
基于 Python 實現binlog2sql
也是 Python 實現但更早出現
- 使用場景
- 選
reverse_sql
:
? 需要快速生成精準回滾 SQL(需明確操作類型和時間范圍)
? 處理大型事務(自動分組優化) - 選
binlog2sql
:
? 需要分析 binlog 詳細內容(支持實時解析)
?持續監控或復雜過濾條件(多操作類型組合過濾,如 GTID / 多庫多表)
? 大 binlog 文件處理,優先binlog2sql -B
(內存優化更可靠)
- 選
8.總結
reverse_sql
在生成回滾 SQL 時更加靈活和高效。reverse_sql
數據閃回工具為數據庫管理和維護提供了極大的便利,特別是在數據恢復方面。它允許用戶在發生錯誤時能夠迅速采取行動,減少數據丟失帶來的損失。然而,為了有效使用此工具,必須注意其實施條件,如確保日志記錄功能正常運行,以及維護良好的備份習慣。此外,了解工具的具體操作方法及其限制也非常重要,這有助于更安全、高效地管理數據庫資源。