误操作后快速恢复数据 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_HOST
- MySQL主机名
- -P MYSQL_PORT, --mysql-port MYSQL_PORT
- MySQL端口号
- -u MYSQL_USER, --mysql-user MYSQL_USER
- MySQL用户名
- -p MYSQL_PASSWD, --mysql-passwd MYSQL_PASSWD
- MySQL密码
- -d MYSQL_DATABASE, --mysql-database MYSQL_DATABASE
- MySQL数据库名
- -c MYSQL_CHARSET, --mysql-charset MYSQL_CHARSET
- MySQL字符集,默认utf8
- --binlog-file BINLOG_FILE
- Binlog文件
- --binlog-pos BINLOG_POS
- Binlog位置,默认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 exit
- Example 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: 0
- greatsql> 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: 0
- greatsql> 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.000002
- Position: 2918
- Binlog_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)
复制代码 执行解析命令
[code] ./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 |