工具分享丨資料閃回工具MyFlash

GreatSQL發表於2024-10-25

工具分享丨資料閃回工具MyFlash

在當今數字化的時代,資料已經成為了企業和個人最寶貴的資產之一。資料不僅僅是簡單的資訊集合,它更是決策的依據、業務的支撐以及創新的源泉。

資料丟失是一種極其危險且令人頭疼的情況。想象一下,企業因系統故障、人為誤操作或遭受惡意攻擊而丟失了關鍵的業務資料,這可能導致業務中斷、客戶流失,甚至面臨法律風險和聲譽損害。

在這樣的背景下,有效的資料管理和保護工具就顯得尤為重要。在之前的社群文章中,有介紹過一款閃回工具binlog2sql但今天介紹的閃回工具相對已有的回滾工具,其增加了更多的過濾選項,效能優於 binlog2sqlmysqlbinlog

  • binlog2sql工具:技術分享 | 測試git上2500星的閃回小工具

除使用工具還可以手動恢復資料,詳見推文:

  • 誤刪GreatSQL資料?別慌,Binlog來幫忙

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(10) NOT NULL DEFAULT 'pending',
  `quantity` int NOT NULL,
  `order_amount` decimal(10,2) NOT NULL,
  `shipping_address` varchar(255) NOT NULL,
  `billing_address` varchar(255) NOT NULL,
  `order_notes` varchar(255) DEFAULT 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 😃

關於 GreatSQL

GreatSQL是適用於金融級應用的國內自主開源資料庫,具備高效能、高可靠、高易用性、高安全等多個核心特性,可以作為MySQL或Percona Server的可選替換,用於線上生產環境,且完全免費併相容MySQL或Percona Server。

相關連結: GreatSQL社群 Gitee GitHub Bilibili

GreatSQL社群:

社群部落格有獎徵稿詳情:https://greatsql.cn/thread-100-1-1.html

image-20230105161905827

技術交流群:

微信:掃碼新增GreatSQL社群助手微信好友,傳送驗證資訊加群

image-20221030163217640

相關文章