工具分享丨数据闪回工具 MyFlash

文摘   科技   2024-10-25 08:00   福建  

* GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源。


在当今数字化的时代,数据已经成为了企业和个人最宝贵的资产之一。数据不仅仅是简单的信息集合,它更是决策的依据、业务的支撑以及创新的源泉。

数据丢失是一种极其危险且令人头疼的情况。想象一下,企业因系统故障、人为误操作或遭受恶意攻击而丢失了关键的业务数据,这可能导致业务中断、客户流失,甚至面临法律风险和声誉损害。

在这样的背景下,有效的数据管理和保护工具就显得尤为重要。在之前的社区文章中,有介绍过一款闪回工具binlog2sql但今天介绍的闪回工具相对已有的回滚工具,其增加了更多的过滤选项,性能优于 binlog2sqlmysqlbinlog

除使用工具还可以手动恢复数据,详见推文:

MyFlash 工具介绍

MyFlash 是由美团点评公司技术工程部开发维护的一个回滚DML操作的工具。该工具通过解析 v4 版本的 binlog,完成回滚操作。相对已有的回滚工具,其增加了更多的过滤选项,让回滚更加容易。

  • 可以针对实例、数据库、表及指定的DML语句进行回滚
  • 如果binlog日志保留,可以闪回到任意时间

该工具开源地址为:https://github.com/Meituan-Dianping/MyFlash

限制

  1. binlog格式必须为row,且binlog_row_image=full

  2. 仅支持 MySQL5.6 与 MySQL5.7

  3. 只能回滚DML(增、删、改)

介绍说只支持 5.6 与 5.7 版本,但在 GreatSQL 8.0.32-26 版本测试中也可用

安装方法

拉取工具仓库,并编译安装

$ git clone https://github.com/Meituan-Dianping/MyFlash.git

动态编译链接

$ gcc -w  `pkg-config --cflags --libs glib-2.0` source/binlogParseGlib.c  -o binary/flashback

若不想每次去重新编译,可以选择使用静态链接,但是该方法需要知道glib库的版本和位置,因此对于每台机器略有不同,请谨慎使用

静态编译链接

$ gcc -w -g `pkg-config --cflags  glib-2.0` source/binlogParseGlib.c   -o binary/flashback /usr/lib64/libglib-2.0.a -lrt

编译完成后,在binary目录下有可执行文件flashback

参数介绍

$ ./binary/flashback -help
Usage:
  flashback [OPTION?]

Help Options:
  -h, --help                  Show help options

Application Options:
  --databaseNames             databaseName to apply. if multiple, seperate by comma(,)
  --tableNames                tableName to apply. if multiple, seperate by comma(,)
  --tableNames-file           tableName to apply. if multiple, seperate by comma(,)
  --start-position            start position
  --stop-position             stop position
  --start-datetime            start time (format %Y-%m-%d %H:%M:%S)
  --stop-datetime             stop time (format %Y-%m-%d %H:%M:%S)
  --sqlTypes                  sql type to filter . support INSERT, UPDATE ,DELETE. if multiple, seperate by comma(,)
  --maxSplitSize              max file size after split, the uint is M
  --binlogFileNames           binlog files to process. if multiple, seperate by comma(,)  
  --outBinlogFileNameBase     output binlog file name base
  --logLevel                  log level, available option is debug,warning,error
  --include-gtids             gtids to process. if multiple, seperate by comma(,)
  --include-gtids-file        gtids to process. if multiple, seperate by comma(,)
  --exclude-gtids             gtids to skip. if multiple, seperate by comma(,)
  --exclude-gtids-file        gtids to skip. if multiple, seperate by comma(,)
  • databaseNames:指定需要回滚的数据库名。多个数据库可以用“,”隔开。如果不指定该参数,相当于指定了所有数据库。
  • tableNames:指定需要回滚的表名。多个表可以用“,”隔开。如果不指定该参数,相当于指定了所有表。
  • start-position:指定回滚开始的位置。如不指定,从文件的开始处回滚。请指定正确的有效的位置,否则无法回滚。
  • stop-position:指定回滚结束的位置。如不指定,回滚到文件结尾。请指定正确的有效的位置,否则无法回滚。
  • start-datetime:指定回滚的开始时间。注意格式必须是 %Y-%m-%d %H:%M:%S。如不指定,则不限定时间。
  • stop-datetime:指定回滚的结束时间。注意格式必须是 %Y-%m-%d %H:%M:%S。如不指定,则不限定时间。
  • sqlTypes:指定需回滚的SQL类型。目前支持的过滤类型是INSERT, UPDATE ,DELETE。多个类型可以用“,”隔开。
  • maxSplitSize:一旦指定该参数,对文件进行固定尺寸的分割(单位为M),过滤条件有效,但不进行回滚操作。该参数主要用来将大的binlog文件切割,防止单次应用的binlog尺寸过大,对线上造成压力。
  • binlogFileNames:指定需要回滚的binlog文件,目前只支持单个文件,后续会增加多个文件支持。经测试已支持多个binlog文件。
  • outBinlogFileNameBase:指定输出的binlog文件前缀,如不指定,则默认为binlog_output_base.flashback。
  • logLevel:仅供开发者使用,默认级别为error级别。在生产环境中不要修改这个级别,否则输出过多。
  • include-gtids:指定需要回滚的gtid,支持gtid的单个和范围两种形式。
  • exclude-gtids:指定不需要回滚的gtid,用法同include-gtids。

最佳实践

此次测试环境情况如下:

  • 数据库:GreatSQL 8.0.32-26
  • 操作系统:Linux debian 6.1.0-22-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.94-1 (2024-06-21) x86_64 GNU/Linux

创建一个test库和students表,并插入 5 条数据

greatsql> CREATE TABLE students (
    id INT AUTO_INCREMENT PRIMARY KEY,
    first_name VARCHAR(50),
    last_name VARCHAR(50),
    age INT,
    grade VARCHAR(10),
    city VARCHAR(50)
);

greatsql> INSERT INTO students (first_name, last_name, age, grade, city) VALUES
('Alice''Smith'18'Grade 12''New York'),
('Bob''Johnson'17'Grade 11''Los Angeles'),
('Charlie''Williams'16'Grade 10''Chicago'),
('David''Brown'15'Grade 9''Houston'),
('Eve''Davis'17'Grade 11''Miami');

模拟误操作将 age 列全部修改为0

greatsql> UPDATE students SET age = 0;
greatsql> SELECT * FROM students;
+----+------------+-----------+------+----------+-------------+
| id | first_name | last_name | age  | grade    | city        |
+----+------------+-----------+------+----------+-------------+
|  1 | Alice      | Smith     |    0 | Grade 12 | New York    |
|  2 | Bob        | Johnson   |    0 | Grade 11 | Los Angeles |
|  3 | Charlie    | Williams  |    0 | Grade 10 | Chicago     |
|  4 | David      | Brown     |    0 | Grade 9  | Houston     |
|  5 | Eve        | Davis     |    0 | Grade 11 | Miami       |
+----+------------+-----------+------+----------+-------------+
5 rows in set (0.00 sec)

记录误操作时间,并使用FLUSH LOGS换一个 Binary Log 记录,查看 Binary Log 文件名

greatsql> SELECT current_timestamp();
+---------------------+
| current_timestamp() |
+---------------------+
| 2024-08-08 14:41:05 |
+---------------------+

greatsql> FLUSH LOGS;
Query OK, 0 rows affected (0.06 sec)

greatsql> SHOW BINARY LOGS;
+---------------+-----------+-----------+
| Log_name      | File_size | Encrypted |
+---------------+-----------+-----------+
| binlog.000006 |      2146 | No        |
| binlog.000007 |       197 | No        |
+---------------+-----------+-----------+

要用闪回需要注意闪回的时间点,例如这次的需求是,闪回到 INSERT INTO 5条语句后

$ mysqlbinlog --no-defaults -v binlog.000006
# 发生误操作的时间`14:33:56`及开始位置`1359`和结束位置`2102`

MyFlash 解析闪回文件

$ ./flashback --databaseNames=test --tableNames=students --start-position='1359' --stop-position='2102' --binlogFileNames=/data/greatsql/binlog.000006 --outBinlogFileNameBase=students

如果确定误操作类型也可以加上--sqlTypes=UPDATE/INSERT/DELETE(多类型用','隔开)

生成文件 students.flashback 并查看

$ mysqlbinlog --no-defaults --base64-output=decode-rows -v students.flashback 
# 可以看到文件中将 UPDATE 语句进行回滚
# ... 节选 ...
### UPDATE `test`.`students`
### WHERE
###   @1=1
###   @2='Alice'
###   @3='Smith'
###   @4=0
###   @5='Grade 12'
###   @6='New York'
### SET
###   @1=1
###   @2='Alice'
###   @3='Smith'
###   @4=18
###   @5='Grade 12'
###   @6='New York'

执行闪回恢复

$ mysqlbinlog --no-defaults --skip-gtids students.flashback |mysql -uroot -p

再次查看数据,已经恢复

greatsql> SELECT * FROM students;
+----+------------+-----------+------+----------+-------------+
| id | first_name | last_name | age  | grade    | city        |
+----+------------+-----------+------+----------+-------------+
|  1 | Alice      | Smith     |   18 | Grade 12 | New York    |
|  2 | Bob        | Johnson   |   17 | Grade 11 | Los Angeles |
|  3 | Charlie    | Williams  |   16 | Grade 10 | Chicago     |
|  4 | David      | Brown     |   15 | Grade 9  | Houston     |
|  5 | Eve        | Davis     |   17 | Grade 11 | Miami       |
+----+------------+-----------+------+----------+-------------+
5 rows in set (0.00 sec)

回滚该文件中指定语句

回滚该文件中所有 INSTER 语句,UPDATE/DELETE语句也同理

$ ./flashback  --sqlTypes='INSERT' --binlogFileNames=binlog.000006
mysqlbinlog students.flashback | mysql -h<host> -u<user> -p

回滚大文件

该工具中有maxSplitSize参数,可用于切割大文件

# 回滚
$ ./flashback --binlogFileNames=binlog.000006
# 切割大文件
$ ./flashback --maxSplitSize=1 --binlogFileNames=students.flashback
# 应用
$ mysqlbinlog students.flashback.000001 | mysql -h<host> -u<user> -p
...
$ mysqlbinlog students.flashback.<N> | mysql -h<host> -u<user> -p

性能测试

和社区上次推荐的 binlog2sql 测试下性能,看哪个恢复速度快

创建 orders 表

CREATE TABLE `orders` (
  `order_id` int NOT NULL AUTO_INCREMENT,
  `customer_id` int NOT NULL,
  `product_id` int NOT NULL,
  `order_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `order_status` char(10NOT NULL DEFAULT 'pending',
  `quantity` int NOT NULL,
  `order_amount` decimal(10,2NOT NULL,
  `shipping_address` varchar(255NOT NULL,
  `billing_address` varchar(255NOT NULL,
  `order_notes` varchar(255DEFAULT NULL,
  PRIMARY KEY (`order_id`),
  KEY `idx_customer_id` (`customer_id`),
  KEY `idx_product_id` (`product_id`),
  KEY `idx_order_date` (`order_date`),
  KEY `idx_order_status` (`order_status`)
ENGINE=InnoDB AUTO_INCREMENT=100001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

创建 Shell 脚本,并插入 110504 条数据

#!/bin/bash
# 插入的订单数量
num_orders=110504  # 你可以根据需要更改插入的数量
# 循环插入数据
for ((i=1; i<=$num_orders; i++))
do
    customer_id=$((RANDOM % 1000 + 1))        # 随机生成1到1000的顾客ID
    product_id=$((RANDOM % 100 + 1))          # 随机生成1到100的产品ID
    order_date=$(date +"%Y-%m-%d %H:%M:%S")    # 当前时间作为订单日期
    order_status="pending"                    # 默认订单状态为pending
    quantity=$((RANDOM % 5 + 1))              # 随机生成1到5的数量
    order_amount=$(echo "scale=2; $((RANDOM % 500 + 50)) + $((RANDOM % 99)) / 100.0" | bc)  # 随机生成50到550之间的订单金额,保留两位小数
    shipping_address="Address $i, City $((RANDOM % 10 + 1)), Country"  # 构造随机的配送地址
    billing_address="Billing Address $i, City $((RANDOM % 10 + 1)), Country"  # 构造随机的账单地址
    order_notes="Order notes for order $i"    # 每个订单有不同的订单备注
    # 构造SQL插入语句
    insert_query="INSERT INTO orders (customer_id, product_id, order_date, order_status, quantity, order_amount, shipping_address, billing_address, order_notes) VALUES ($customer_id$product_id, '$order_date', '$order_status', $quantity$order_amount, '$shipping_address', '$billing_address', '$order_notes');"

    # 执行插入语句
    mysql -uroot -p test -e "$insert_query"
done
echo "$num_orders 条订单数据插入完成。"

退出后执行脚本,并查看数据量

# 执行脚本
$ bash insert_students.sh

greatsql> SELECT COUNT(*) FROM orders;  
+----------+
| count(*) |
+----------+
|   110504 |
+----------+
1 row in set (0.01 sec)

MyFlash 测试

模拟误操作删除数据

# 误操作删除数据
greatsql> DELETE FROM orders WHERE 1=1;
Query OK, 110504 rows affected (1.59 sec)

# 记录误操作时间
greatsql> SELECT current_timestamp();
+---------------------+
| current_timestamp() |
+---------------------+
| 2024-08-08 17:51:10 |
+---------------------+
1 row in set (0.00 sec)

# 启用新 binlog 日志
greatsql> FLUSH LOGS;
Query OK, 0 rows affected (0.06 sec)

# 查看旧 binlog 日志
greatsql> SHOW BINARY LOGS;
+---------------+-----------+-----------+
| Log_name      | File_size | Encrypted |
+---------------+-----------+-----------+
| binlog.000008 | 101319050 | No        |
| binlog.000009 |       197 | No        |
+---------------+-----------+-----------+
2 rows in set (0.01 sec)

查看 Binary Log 日志,找到误操作位置

$ mysqlbinlog --no-defaults -v binlog.000008
# 开始位置 86092119 时间 17:50:52  结束 101319006

使用 MyFlash 生成恢复的 Binary Log

$ time ./flashback --databaseNames=test --tableNames=orders --start-position='86092119' --stop-position='101319006' --binlogFileNames=/data/greatsql/binlog.000008 --outBinlogFileNameBase=orders

real    0m0.138s
user    0m0.029s
sys     0m0.065s

查看生成的文件,并使用 mysqlbinlog 回滚恢复

$ ls -l
-rwxr-xr-x 1 root root   957744 Aug  5 11:26 flashback
-rw-r--r-- 1 root root 15430854 Aug  9 10:52 orders.flashback

闪回恢复
$ mysqlbinlog --no-defaults --skip-gtids orders.flashback |mysql -uroot -p

real    0m9.414s
user    0m0.433s
sys     0m0.082s

查看数据恢复情况

greatsql> SELECT COUNT(*) FROM orders;  
+----------+
| count(*) |
+----------+
|   110504 |
+----------+
1 row in set (0.01 sec)

用切割的方式导入

# 每个文件设定大小为 1M (可按需求自定义)
$ ./flashback --maxSplitSize=1 --binlogFileNames=orders.flashback
# 此时就会多出很多 binlog_output_base 文件
-rw-r--r-- 1 root root 1.1M Aug  9 13:55 binlog_output_base.000001
... 省略 ...
-rw-r--r-- 1 root root 640K Aug  9 13:55 binlog_output_base.000015

切割后的文件名无法修改,只能叫 binlog_output_base

如果切割的文件多了,需要写个小脚本导入这些切割后的文件

$ vim batch_rollback.sh

# 循环导入每个文件
for i in $(seq -w 1 15); do
    FILENAME="binlog_output_base.0000$i"
    mysqlbinlog --no-defaults --skip-gtids $FILENAME | mysql -uroot -p
done

运行 batch_rollback.sh导入

$ time batch_rollback.sh
  real    0m9.071s
  user    0m0.454s
  sys     0m0.200s
  
greatsql> SELECT COUNT(*) FROM orders;  
+----------+
| count(*) |
+----------+
|   110504 |
+----------+
1 row in set (0.02 sec)

根据 MyFlash 官方的测试结果和 mysqlbinlog、binlog2sql 两款工具对比,恢复100万条数据

  • MyFlash 耗时 2.774s
  • mysqlbinlog 耗时 6.217s
  • binlog2sql 耗时 581.319s

总结

  • MyFlash 使用较方便,回滚耗时短,大数据量恢复可以使用该工具
  • binlog2sql 需要 Python 环境,可能会造成字符问题,MyFlash 比较稳定



Enjoy GreatSQL :)

<往 期 推 荐>
MariaDB 和 GreatSQL 性能差异背后的真相
GreatSQL 在SQL中使用 HINT 语法修改会话变量
某市驾驶培训监管服务平台 GreatSQL 数据库适配之旅
误删 GreatSQL 数据?别慌,Binlog来帮忙
展会 | 斩获殊荣!GreatSQL亮相2024 OSCAR开源产业大会  彰显开源实力

《用三分钟学会一个MySQL知识》

<关于 GreatSQL>

GreatSQL数据库是一款开源免费数据库,可在普通硬件上满足金融级应用场景,具有高可用、高性能、高兼容、高安全等特性,可作为MySQL或Percona Server for MySQL的理想可选替换。

💻社区官网: https://greatsql.cn/ 
Gitee  https://gitee.com/GreatSQL/GreatSQL
GitHub  https://github.com/GreatSQL/

🆙BiliBili  : https://space.bilibili.com/1363850082

(对文章有疑问或见解可去社区官网提出哦~)

加入微信交流群
加入QQ交流群

想看更多技术好文,点个"在看"吧!

GreatSQL社区
专注GreatSQL数据库及相关产品
 最新文章