前言
使用數(shù)據(jù)庫過程中難免有出錯的時候,假如不小心刪除了一張表,或者錯誤執(zhí)行了update語句,那么這個時候想要恢復數(shù)據(jù)就需要用到數(shù)據(jù)閃回工具,如:binlog2sql和myflash等。但因為binlog2sql工具要求數(shù)據(jù)庫在線,即需要連接數(shù)據(jù)庫進行binlog解析才能轉換為sql,無法直接對binlog文件進行解析,使用條件上相對苛刻,所以沒有考慮。
介紹
myflash是美團研發(fā)的一款開源的數(shù)據(jù)庫閃回工具,他可以用來回滾DML操作。
使用條件
- MySQL數(shù)據(jù)庫必須開啟binlog
- binlog格式必須為row,且binlog_row_image=full
- 僅支持5.6與5.7
- 只能回滾DML(增、刪、改)
下載與安裝
下載地址為:https://github.com/Meituan-Dianping/MyFlash
按照下圖方式下載,可以不用安裝git。

首先要安裝編譯所必須的依賴包
[root@hadoop101 /]# yum install -y gcc pkg-config glib2 libgnomeui-devel
將壓縮包上傳到服務器后使用unzip命令解壓。
[root@hadoop101 package]# pwd
/opt/package
[root@hadoop101 package]# ls
myflash-master.zip mysql-5.7.24-linux-glibc2.12-x86_64.tar.gz
[root@hadoop101 package]# unzip -d /opt/ myflash-master.zip
...解壓文件列表過長不展示
[root@hadoop101 MyFlash-master]# cd /opt/MyFlash-master/
[root@hadoop101 MyFlash-master]# ls
binary binlog_output_base.flashback build.sh doc License.md README.md source testbinlog
如果下載的是zip包,他會有一個編譯好的程序為解壓路徑/binary/flashback,也可以再次編譯,進入解壓后的目錄,編譯并安裝
[root@hadoop101 MyFlash-master]# gcc -w `pkg-config --cflags --libs glib-2.0` source/binlogParseGlib.c -o binary/flashback
安裝完之后是沒有環(huán)境變量的,只能使用絕對路徑調用工具,使用--help可以查看命令的使用方式
[root@hadoop101 binary]# /opt/MyFlash-master/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(,)
--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
--exclude-gtids gtids to skip
參數(shù)解釋,這里我直接借用了別人的,有些東西我也沒用過,所以不確保是否可用。
| 參數(shù) | 釋義 |
|---|---|
| --databaseNames | 需要閃回的數(shù)據(jù)庫名稱,如果有多個數(shù)據(jù)庫,用逗號”,”隔開。 |
| --tableNames | 要閃回的表名稱,如果有多個表,用逗號”,”隔開。 |
| --start-position | 閃回的起始位置,如不指定,從文件開始處回滾。 |
| --stop-position | 閃回的終止位置,如不指定,回滾到文件結尾。 |
| --start-datetime | 閃回的開始時間。 |
| --stop-datetime | 閃回的終止時間。 |
| --sqlTypes | 指定需要回滾的sql類型,支持INSERT、UPDATE、DELETE,多個類型使用逗號”,”分開。 |
| --maxSplitSize | 對文件進行固定尺寸的切割,以防止單次應用binlog尺寸較大,對線上造成壓力。 |
| --binlogFileNames | 指定需要回滾的binlog文件,美團文檔說目前只支持單個binlog文件,經(jīng)測試已經(jīng)支持多個binlog文件同時閃回。 |
| --outBinlogFileNameBase | 指定輸出的binlog文件前綴,如不指定,則默認為binlog_output_base.flashback。 |
| logLevel | 僅供開發(fā)者使用,默認級別為error級別。在生產(chǎn)環(huán)境中不要修改這個級別,否則輸出過多。 |
| include-gtids | 指定需要回滾的gtid,支持gtid的單個和范圍兩種形式。 |
| exclude-gtids | 指定不需要回滾的gtid,用法同include-gtids。 |
事先準備
首先我準備了一個單節(jié)點的mysql,然后刷新了binlog,之后會建表寫入演示數(shù)據(jù),然后模擬誤操作再演示回滾數(shù)據(jù)。
mysql> flush logs;
Query OK, 0 rows affected (0.02 sec)
mysql> show master status ;
+------------------+----------+--------------+------------------+--------------------------------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+--------------------------------------------+
| mysql-bin.000005 | 194 | | | 2ec63aca-cd00-11ec-ba97-000c29648fd7:1-144 |
+------------------+----------+--------------+------------------+--------------------------------------------+
1 row in set (0.00 sec)
mysql> create database demo;
Query OK, 1 row affected (0.00 sec)
mysql> create table `time`(
-> `id` int primary key auto_increment,
-> `string` varchar(50),
-> `create_time` datetime
-> );
Query OK, 0 rows affected (0.01 sec)
mysql> insert into demo.`time` values(null,'!@#',now());
Query OK, 1 row affected (0.01 sec)
mysql> insert into demo.`time` values(null,'*&^',now());
Query OK, 1 row affected (0.00 sec)
mysql> insert into demo.`time` values(null,'^%$',now());
Query OK, 1 row affected (0.00 sec)
mysql> select * from demo.`time`;
+----+--------+---------------------+
| id | string | create_time |
+----+--------+---------------------+
| 1 | !@# | 2022-05-06 17:21:48 |
| 2 | *&^ | 2022-05-06 17:21:53 |
| 3 | ^%$ | 2022-05-06 17:21:58 |
+----+--------+---------------------+
3 rows in set (0.00 sec)
刪除數(shù)據(jù)
此時刪除整張表的數(shù)據(jù),但保留表結構,一般誤操作都是在圖形化界面誤操作的,所以我這里使用navicat模擬刪除。

這里千萬不要點刪除表或者截斷表,刪除表的命令的drop,截斷表的命令是truncate,這兩個都是DDL語句,binlog是不會記錄詳細的刪除信息的,一旦執(zhí)行操作將無法回滾,是myflash無法拯救的。
此時可以查看一下mysql的binlog文件,看看binlog的情況。
[root@hadoop101 binlog]# pwd
/data/mysql_data/binlog
[root@hadoop101 binlog]# ls
mysql-bin.000001 mysql-bin.000002 mysql-bin.000003 mysql-bin.000004 mysql-bin.000005 mysql-bin.index
[root@hadoop101 binlog]# /opt/mysql_6033/bin/mysqlbinlog -vv --base64-output=DECODE-ROWS mysql-bin.000005
......
create database demo
/*!*/;
# at 353
......
create table `time`( `id` int primary key auto_increment, `string` varchar(50), `create_time` datetime )
/*!*/;
# at 596
......
# at 865
#220506 17:21:48 server id 1000 end_log_pos 914 CRC32 0x305f5f15 Write_rows: table id 112 flags: STMT_END_F
### INSERT INTO `demo`.`time`
### SET
### @1=1 /* INT meta=0 nullable=0 is_null=0 */
### @2='!@#' /* VARSTRING(200) meta=200 nullable=1 is_null=0 */
### @3='2022-05-06 17:21:48' /* DATETIME(0) meta=0 nullable=1 is_null=0 */
# at 914
......
# at 1214
#220506 17:21:53 server id 1000 end_log_pos 1263 CRC32 0x834574e8 Write_rows: table id 112 flags: STMT_END_F
### INSERT INTO `demo`.`time`
### SET
### @1=2 /* INT meta=0 nullable=0 is_null=0 */
### @2='*&^' /* VARSTRING(200) meta=200 nullable=1 is_null=0 */
### @3='2022-05-06 17:21:53' /* DATETIME(0) meta=0 nullable=1 is_null=0 */
......
# at 1563
#220506 17:21:58 server id 1000 end_log_pos 1612 CRC32 0xa58ebd4d Write_rows: table id 112 flags: STMT_END_F
### INSERT INTO `demo`.`time`
### SET
### @1=3 /* INT meta=0 nullable=0 is_null=0 */
### @2='^%$' /* VARSTRING(200) meta=200 nullable=1 is_null=0 */
### @3='2022-05-06 17:21:58' /* DATETIME(0) meta=0 nullable=1 is_null=0 */
......
#220506 17:22:24 server id 1000 end_log_pos 1951 CRC32 0xc374eb11 Delete_rows: table id 112 flags: STMT_END_F
### DELETE FROM `demo`.`time`
### WHERE
### @1=1 /* INT meta=0 nullable=0 is_null=0 */
### @2='!@#' /* VARSTRING(200) meta=200 nullable=1 is_null=0 */
### @3='2022-05-06 17:21:48' /* DATETIME(0) meta=0 nullable=1 is_null=0 */
### DELETE FROM `demo`.`time`
### WHERE
### @1=2 /* INT meta=0 nullable=0 is_null=0 */
### @2='*&^' /* VARSTRING(200) meta=200 nullable=1 is_null=0 */
### @3='2022-05-06 17:21:53' /* DATETIME(0) meta=0 nullable=1 is_null=0 */
### DELETE FROM `demo`.`time`
### WHERE
### @1=3 /* INT meta=0 nullable=0 is_null=0 */
### @2='^%$' /* VARSTRING(200) meta=200 nullable=1 is_null=0 */
### @3='2022-05-06 17:21:58' /* DATETIME(0) meta=0 nullable=1 is_null=0 */
# at 1951
......
多余的內容我用省略號代替了,主要是想展示在binlog中包含了所有有價值的內容,比如建表,insert和delete,而且對整張表執(zhí)行delete命令時,他會一條一條數(shù)據(jù)刪除,刪除的每條數(shù)據(jù)都完好的保存在binlog中,這也為回滾提供了便捷。
回滾
使用myfalsh工具對binlog進行回滾,他會生成一個新的以.flashback為后綴的文件,這是一個已經(jīng)回滾完的binlog文件,上面binlog中的delete語句會被轉換為insert語句,然后使用mysqlbinlog命令就可以導入此文件到mysql實例中,如此便完成回滾。
執(zhí)行命令:
[root@hadoop101 binlog]# /opt/MyFlash-master/binary/flashback --databaseNames="demo" --tableNames="time" --start-datetime="2022-05-06 17:00:00" --stop-datetime="2022-05-06 18:00:00" --sqlTypes="UPDATE,DELETE" --binlogFileNames=/data/mysql_data/binlog/mysql-bin.000005 --outBinlogFileNameBase=/tmp/rollbak.sql
命令中要回滾的數(shù)據(jù)庫為demo,要回滾的表是time,誤操作的開始時間,即回滾開始時間為2022-05-06 17:00:00,回滾結束時間為2022-05-06 18:00:00,要回滾的語句指定為UPDATE和DELETE,要解析的binlog文件是mysql-bin.000005,回滾后的文件保存在/tmp/下,文件名前綴為rollbak.sql。如果有多個數(shù)據(jù)庫和多張表記得用逗號隔開。
然后會在/tmp/路徑下生成一個叫rollbak.sql.flashback的文件,該文件使用mysqlbinlog命令解析后如下圖

最后將此文件導入即可:
[root@hadoop101 binlog]# /opt/mysql_6033/bin/mysqlbinlog --skip-gtids /tmp/rollbak.sql.flashback | /opt/mysql_6033/bin/mysql -h 127.0.0.1 -P 6033 -u root -p
這里注意,如果mysql數(shù)據(jù)庫使用了gtid,則導入回滾數(shù)據(jù)時要添加--skip-gtids的參數(shù),否則會報錯。
最后登錄數(shù)據(jù)庫查看

額外的內容
insert和delete的回滾很容易,兩者的操作是相悖的,只需要直接替換關鍵詞即可,而update是特殊的,在某些場景下不建議在--sqlTypes參數(shù)中添加UPDATE。
比如原字段A的值是1,現(xiàn)將其改為99,那么在binlog中會記錄下“update table set A=99 where A=1”,如果將其回滾,這條語句就會變成“update table set A=1 where A=99”,而如果在誤操作delete之間有正常的update,那么回滾之后數(shù)據(jù)就會異常,正常應該執(zhí)行的update語句被回滾回去了。
所以一定要清楚自己的誤操作到底操作了什么,針對誤操作的類型進行單獨的回滾是比較好的。