昨天突然有個客戶說誤操作,自己刪除了大量數據,cto直接將我拉到一個討論組里,說要幫他們恢復數據。他們自己挖的坑,打算讓開發那邊根據業務日志去恢復,被告知只記錄的刪除主鍵這樣的信息,物理刪除,無能為力。
上服務器看了下記錄的日志,發現好幾臺上面都有被誤刪的記錄輸出。阿里RDS雖然可以克隆一個恢復到刪除時間點前的實例,但這散落的幾萬個id找起來費力,還有就是幾個表之間關聯的數據也要恢復,覺得麻煩。
想到 MySQL 的閃回方案。以前看過好幾篇相關文章,甚至差點自己用MySQL擼一個來解析binlog,反轉得到回滾sql,實在沒空,這下要急用了。趕緊找了下網上“現成的方案”。
正文開始
MySQL(含阿里RDS)快速閃回可以說是對數據庫誤操作的后悔藥,flashback功能可以將數據庫返回到誤操作之前。但是即使oracle數據庫也只支持短時間內的閃回。
網上現有開源的MySQL閃回實現,原理都是解析binlog,生成反向sql: (必須為row模式)
-
對于 delete 操作,生成insert (DELETE_ROWS_EVENT)
-
對于 update 操作,交換binlog里面值的順序 (UPDATE_ROWS_EVENT)
-
對于 insert 操作,反向生成delete (WRITE_ROWS_EVENT)
-
對于多個event,要逆向生成sql
上面兩種實現方式,都是通過 python-mysql-replication 包,模擬出原庫的一個從庫,然后 show binary logs 來獲取binlog,發起同步binlog的請求,再解析EVENT。但是阿里云 RDS 的binlog在同步給從庫之后, 很快就被 purge 掉了 。如果要恢復 昨天 的 部分數據 ,兩種方案都是拿不到binlog的。也就是閃回的時間有限。
還有一些比較簡單的實現,就是解析 binlog 物理文件,實現回滾,如 binlog-rollback.pl ,試過,但是速度太慢。
為了不影響速度,又想使用比較成熟的閃回方案,我們可以這樣做:
-
借助一個自建的 mysqld 實例,將已purge掉的binlog拷貝到該實例的目錄下
-
在自建實例里,提前創建好需要恢復的表(結構),因為工具需要連接上來從 information_schema.columns 獲取元數據信息
-
拷貝的時候,可以替換掉mysql實例自己的binlog文件名,保持連續
-
可能要修改 mysql-bin.index,確保文件名還能被mysqld識別到
-
重啟mysql實例,show binary logs 看一下是否在列表里面
-
接下來就可以使用上面任何一種工具,模擬從庫,指定一個binlog文件,開始時間,結束時間,得到回滾SQL
-
再根據業務邏輯,篩選出需要的sql
總之就是借助另外一個mysql,把binlog event傳輸過來。溫馨提示:
-
兩個實例間版本不要跨度太大
-
注意文件權限
-
如果原庫開啟了gtid,這個自建實例也要開啟gtid
示例:
python?mysqlbinlog_back.py?--host="localhost"?--username="ecuser"?--password="ecuser"?--port=3306? --schema=dbname?--tables="t_xx1,t_xx2,t_xx3"?-S?"mysql-bin.000019"?-E?"2017-03-02?13:00:00"?-N?"2017-03-02?14:09:00"?-I?-U ===log?will?also??write?to?.//mysqlbinlog_flashback.log=== parameter={'start_binlog_file':?'mysql-bin.000019',?'stream':?None,?'keep_data':?True, ?'file':?{'data_create':?None,?'flashback':?None,?'data':?None},?'add_schema_name':?False,?'start_time':?None,? ?'keep_current_data':?False,?'start_to_timestamp':?1488430800, ?'mysql_setting':?{'passwd':?'ecuser',?'host':?'localhost',?'charset':?'utf8',?'port':?3306,?'user':?'ecuser'}, ?'table_name':?'t_xx1,t_xx2,t_xx3',?'skip_delete':?False,?'schema':?'dbname',?'stat':?{'flash_sql':?{}}, ?'table_name_array':?['t_xx1',?'t_xx2',?'t_xx3'], ?'one_binlog_file':?False,?'output_file_path':?'./log',?'start_position':?4,?'skip_update':?True, ?'dump_event':?False,?'end_to_timestamp':?1488434940,?'skip_insert':?True,?'schema_array':?['dbname'] } scan?10000?events?....from?binlogfile=mysql-bin.000019,timestamp=2017-03-02T11:42:14 scan?20000?events?....from?binlogfile=mysql-bin.000019,timestamp=2017-03-02T11:42:29 ...
提示:
binlog為ROW格式,dml影響的每一行都會記錄兩個event:Table_map和Row_log。而table_map里面的table_id并不會影響它在哪個實例上應用,這個id可以認為是邏輯上,記錄表結構版本的機制 —— 當它在 table_definition_cache 沒有找到表定義時,id自增1,分配給要記錄到binlog的表。
mysqlbinlog_back.py 使用經驗 :
-
務必指定庫名、表明,開始的binlog文件名,起始時間,結束時間。可以加快scan的速度。
-
根據恢復的需要,選擇 -I, -U, -D,指定回滾哪些類型的操作。
-
如果只是恢復部分表數據(非完全閃回),做不到關聯表的正確恢復。比如需要恢復delete數據,但無法恢復業務里因為delete引起其它表MySQL的數據,除非完全閃回。
-
不支持表字段是 enum 類型的,比如 t_xx3 的f_do_type字段。可以把自建實例上的enum定義改成int。