DBA或開發人員,有時會誤刪或者誤更新數據,若是是線上環境而且影響較大,就須要能快速回滾。傳統恢復方法是利用備份重搭實例,再應用去除錯誤sql後的binlog來恢復數據。此法費時費力,甚至須要停機維護,並不適合快速回滾。也有團隊利用LVM快照來縮短恢復時間,但快照的缺點是會影響mysql的性能。php
MySQL閃回(flashback)利用binlog直接進行回滾,能快速恢復且不用停機。本文將介紹閃回原理,給出筆者的實戰經驗,並對現存的閃回工具做比較。html
某天,小明因種種緣由,誤刪了大批線上用戶表的數據。他急忙找到公司DBA請求幫助,「客服電話已被打爆,大量用戶投訴沒法登錄,領導很是惱火。請問多久能恢復數據?」DBA一臉懵逼,沉默十秒後,伸出一根手指。「你的意思是一分鐘就能恢復?太好了。」小明終於有些放鬆,露出了一絲笑容。「不,咱們中有我的將會離開公司。」DBA沉痛的說道。python
勿讓悲劇發生,儘早將此文轉給公司DBA。mysql
binlog概述git
MySQL binlog以event的形式,記錄了MySQL server從啓用binlog以來全部的變動信息,可以幫助重現這之間的全部變化。MySQL引入binlog主要有兩個目的:一是爲了主從複製;二是某些備份還原操做後須要從新應用binlog。github
有三種可選的binlog格式,各有優缺點:sql
利用binlog閃回,須要將binlog格式設置爲row。row模式下,一條使用innodb的insert會產生以下格式的binlog:shell
# at 1129 #161225 23:15:38 server id 3773306082 end_log_pos 1197 Query thread_id=1903021 exec_time=0 error_code=0 SET TIMESTAMP=1482678938/*!*/; BEGIN /*!*/; # at 1197 #161225 23:15:38 server id 3773306082 end_log_pos 1245 Table_map: `test`.`user` mapped to number 290 # at 1245 #161225 23:15:38 server id 3773306082 end_log_pos 1352 Write_rows: table id 290 flags: STMT_END_F BINLOG ' muJfWBPiFOjgMAAAAN0EAAAAACIBAAAAAAEABHRlc3QABHVzZXIAAwMPEQMeAAAC muJfWB7iFOjgawAAAEgFAAAAACIBAAAAAAEAAgAD//gBAAAABuWwj+i1tVhK1hH4AgAAAAblsI/p krFYStYg+AMAAAAG5bCP5a2ZWE/onPgEAAAABuWwj+adjlhNeAD4BQAAAAJ0dFhRYJM= '/*!*/; # at 1352 #161225 23:15:38 server id 3773306082 end_log_pos 1379 Xid = 5327954 COMMIT/*!*/;
閃回原理安全
既然binlog以event形式記錄了全部的變動信息,那麼咱們把須要回滾的event,從後往前回滾回去便可。bash
對於單個event的回滾,咱們以表test.user來演示原理
mysql> show create table test.user\G *************************** 1. row *************************** Table: user Create Table: CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(10) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8
對於delete操做,咱們從binlog提取出delete信息,生成的回滾語句是insert。(注:爲了方便解釋,咱們用binlog2sql將原始binlog轉化成了可讀SQL)
原始:DELETE FROM `test`.`user` WHERE `id`=1 AND `name`='小趙'; 回滾:INSERT INTO `test`.`user`(`id`, `name`) VALUES (1, '小趙');
對於insert操做,回滾SQL是delete。
原始:INSERT INTO `test`.`user`(`id`, `name`) VALUES (2, '小錢'); 回滾:DELETE FROM `test`.`user` WHERE `id`=2 AND `name`='小錢';
對於update操做,回滾sql應該交換SET和WHERE的值。
原始:UPDATE `test`.`user` SET `id`=3, `name`='小李' WHERE `id`=3 AND `name`='小孫'; 回滾:UPDATE `test`.`user` SET `id`=3, `name`='小孫' WHERE `id`=3 AND `name`='小李';
真實的閃回場景中,最關鍵的是能快速篩選出真正須要回滾的SQL。
咱們使用開源工具binlog2sql來進行實戰演練。binlog2sql由美團點評DBA團隊(上海)出品,屢次在線上環境作快速回滾。
首先咱們安裝binlog2sql:
shell> git clone https://github.com/danfengcao/binlog2sql.git && cd binlog2sql shell> pip install -r requirements.txt
背景:小明在11:44時誤刪了test庫user表大批的數據,須要緊急回滾。
test庫user表原有數據 mysql> select * from user; +----+--------+---------------------+ | id | name | addtime | +----+--------+---------------------+ | 1 | 小趙 | 2013-11-11 00:04:33 | | 2 | 小錢 | 2014-11-11 00:04:48 | | 3 | 小孫 | 2016-11-11 20:25:00 | | 4 | 小李 | 2013-11-11 00:00:00 | ......... +----+--------+---------------------+ 16384 rows in set (0.04 sec) 11:44時,user表大批數據被誤刪除。與此同時,正常業務數據是在繼續寫入的 mysql> delete from user where addtime>'2014-01-01'; Query OK, 16128 rows affected (0.18 sec) mysql> select count(*) from user; +----------+ | count(*) | +----------+ | 261 | +----------+
恢復數據步驟:
登陸mysql,查看目前的binlog文件
mysql> show master logs; +------------------+-----------+ | Log_name | File_size | +------------------+-----------+ | mysql-bin.000053 | 168652863 | | mysql-bin.000054 | 504549 | +------------------+-----------+
2. 最新的binlog文件是mysql-bin.000054。咱們的目標是篩選出須要回滾的SQL,因爲誤操做人只知道大體的誤操做時間,咱們首先根據時間作一次過濾。只須要解析test庫user表。(注:若是有多個sql誤操做,則生成的binlog可能分佈在多個文件,需解析多個文件) ```bash shell> python binlog2sql/binlog2sql.py -h127.0.0.1 -P3306 -uadmin -p'admin' -dtest -tuser --start-file='mysql-bin.000054' --start-datetime='2016-12-26 11:44:00' --stop-datetime='2016-12-26 11:50:00' > /tmp/raw.sql raw.sql 輸出: DELETE FROM `test`.`user` WHERE `addtime`='2014-11-11 00:04:48' AND `id`=2 AND `name`='小錢' LIMIT 1; #start 257427 end 265754 time 2016-12-26 11:44:56 DELETE FROM `test`.`user` WHERE `addtime`='2015-11-11 20:25:00' AND `id`=3 AND `name`='小孫' LIMIT 1; #start 257427 end 265754 time 2016-12-26 11:44:56 ... DELETE FROM `test`.`user` WHERE `addtime`='2016-12-14 23:09:07' AND `id`=24530 AND `name`='tt' LIMIT 1; #start 257427 end 504272 time 2016-12-26 11:44:56 INSERT INTO `test`.`user`(`addtime`, `id`, `name`) VALUES ('2016-12-10 00:04:33', 32722, '小王'); #start 504299 end 504522 time 2016-12-26 11:49:42 ...
根據位置信息,咱們肯定了誤操做sql來自同一個事務,準確位置在257427-504272之間(binlog2sql對於同一個事務會輸出一樣的start position)。再根據位置過濾,使用 -B 選項生成回滾sql,檢查回滾sql是否正確。(注:真實場景下,生成的回滾SQL常常會須要進一步篩選。結合grep、編輯器等)
shell> python binlog2sql/binlog2sql.py -h127.0.0.1 -P3306 -uadmin -p'admin' -dtest -tuser --start-file='mysql-bin.000054' --start-position=257427 --stop-position=504272 -B > /tmp/rollback.sql rollback.sql 輸出: INSERT INTO test
.user
(addtime
, id
, name
) VALUES ('2016-12-14 23:09:07', 24530, 'tt'); #start 257427 end 504272 time 2016-12-26 11:44:56 INSERT INTO test
.user
(addtime
, id
, name
) VALUES ('2016-12-12 00:00:00', 24529, '小李'); #start 257427 end 504272 time 2016-12-26 11:44:56 ... INSERT INTO test
.user
(addtime
, id
, name
) VALUES ('2014-11-11 00:04:48', 2, '小錢'); #start 257427 end 265754 time 2016-12-26 11:44:56
shell> wc -l /tmp/rollback.sql
16128 /tmp/rollback.sql ```
與業務方確認回滾sql沒問題,執行回滾語句。登陸mysql,確認回滾成功。
shell> mysql -h127.0.0.1 -P3306 -uadmin -p'admin' < /tmp/rollback.sql mysql> select count(*) from user;
+----------+ | count(*) | +----------+ | 16389 | +----------+
###TIPS * 閃回的關鍵是快速篩選出真正須要回滾的SQL。 * 先根據庫、表、時間作一次過濾,再根據位置作更準確的過濾。 * 因爲數據一直在寫入,要確保回滾sql中不包含其餘數據。可根據是不是同一事務、誤操做行數、字段值的特徵等等來幫助判斷。 * 執行回滾sql時若有報錯,須要查實具體緣由,通常是由於對應的數據已發生變化。因爲是嚴格的行模式,只要有惟一鍵(包括主鍵)存在,就只會報某條數據不存在的錯,沒必要擔憂會更新不應操做的數據。 * 若是待回滾的表與其餘表有關聯,要與開發說明回滾和不回滾各自的反作用,再肯定方案。 * 回滾後數據變化,可能對用戶和線上應用形成困惑(相似幻讀)。 ####再重複下最重要的兩點:**篩選出正確SQL**!**溝通清楚**! 閃回工具 === MySQL閃回特性最先由阿里彭立勳開發,彭在2012年給官方提交了一個patch,並對[閃回設計思路](http://www.penglixun.com/tech/database/mysql_flashback_feature.html)作了說明(設計思路頗有啓發性,強烈推薦閱讀)。可是由於種種緣由,業內安裝這個patch的團隊至今仍是少數,真正應用到線上的更是少之又少。彭以後,又有多位人員針對不一樣mysql版本不一樣語言開發了閃回工具,原理用的都是彭的思路。 我將這些閃回工具按實現方式分紅了三類。 * 第一類是以patch形式集成到官方工具mysqlbinlog中。以彭提交的patch爲表明。 > 優勢 > > * 上手成本低。mysqlbinlog原有的選項都能直接利用,只是多加了一個閃回選項。閃回特性將來有可能被官方收錄。 > * 支持離線解析。 > > 缺點 > > * 兼容性差、項目活躍度不高。因爲binlog格式的變更,若是閃回工具做者不及時對補丁升級,則閃回工具將沒法使用。目前已有多位人員分別針對mysql5.5,5.6,5.7開發了patch,部分項目代碼公開,但整體上活躍度都不高。 > * 難以添加新功能,實戰效果欠佳。在實戰中,常常會遇到現有patch不知足需求的狀況,好比要加個表過濾,很簡單的一個需求,代碼改動也不會大,但對大部分DBA來講,改mysql源碼仍是很困難的事。 > * 安裝稍顯麻煩。須要對mysql源碼打補丁再編譯生成。 > 這些缺點,可能都是閃回沒有流行開來的緣由。 * 第二類是獨立工具,經過假裝成slave拉取binlog來進行處理。以binlog2sql爲表明。 > 優勢 > > * 兼容性好。假裝成slave拉binlog這項技術在業界應用的很是普遍,多個開發語言都有這樣的活躍項目,MySQL版本的兼容性由這些項目搞定,閃回工具的兼容問題再也不突出。 > * 添加新功能的難度小。更容易被改形成DBA本身喜歡的形式。更適合實戰。 > * 安裝和使用簡單。 > > 缺點 > > * 必須開啓MySQL server。 * 第三類是簡單腳本。先用mysqlbinlog解析出文本格式的binlog,再根據回滾原理用正則進行匹配並替換。 > 優勢 > > * 腳本寫起來方便,每每能快速搞定某個特定問題。 > * 安裝和使用簡單。 > * 支持離線解析。 > > 缺點 > > * 通用性很差。 > * 可靠性很差。 就目前的閃回工具而言,線上環境的閃回,筆者建議使用binlog2sql,離線解析使用mysqlbinlog。 ###關於DDL的flashback 本文所述的flashback僅針對DML語句的快速回滾。但若是誤操做是DDL的話,是沒法利用binlog作快速回滾的,由於即便在row模式下,binlog對於DDL操做也不會記錄每行數據的變化。要實現DDL快速回滾,必須修改MySQL源碼,使得在執行DDL前先備份老數據。目前有多個mysql定製版本實現了DDL閃回特性,阿里林曉斌團隊提交了patch給MySQL官方,MariaDB預計在不久後加入包含DDL的flashback特性。DDL閃回的反作用是會增長額外存儲。考慮到其應用頻次實在太低,本文不作詳述,有興趣的同窗能夠本身去了解,重要的幾篇文章我在參考資料中作了引用。 有任何問題,或有mysql閃回相關的優秀工具優秀文章遺漏,煩請告知。 danfengcao.info@gmail.com 參考資料 ============== [1] MySQL Internals Manual , [Chapter 20 The Binary Log](http://dev.mysql.com/doc/internals/en/binary-log.html) [2] 彭立勳,[MySQL下實現閃回的設計思路](http://www.penglixun.com/tech/database/mysql_flashback_feature.html) [3] Lixun Peng, [Provide the flashback feature by binlog](https://bugs.mysql.com/bug.php?id=65178) [4] 王廣友,[mysqlbinlog flashback 5.6徹底使用手冊與原理](http://www.cnblogs.com/youge-OneSQL/p/5249736.html) [5] 姜承堯, [拿走不謝,Flashback for MySQL 5.7](http://mp.weixin.qq.com/s?__biz=MjM5MjIxNDA4NA==&mid=2649737874&idx=1&sn=a993322ae58db541c2cf4d9a1efa3063&chksm=beb2d7b989c55eafb7ddcadb28f45bb6018b3e9e65df20b30217fe8cb26d3d444d58076f2d76&mpshare=1&scene=1&srcid=1228ta3qs3QIN6FS4AUCuCKm#rd) [6] 林曉斌, [MySQL閃回方案討論及實現](http://dinglin.iteye.com/blog/1539167) [7] xiaobin lin, [flashback from binlog for MySQL](https://bugs.mysql.com/bug.php?id=65861) [8] mariadb.com, [AliSQL and some features that have made it into MariaDB Server](https://mariadb.com/resources/blog/alisql-and-some-features-have-made-it-mariadb-server) [9] danfengcao, [binlog2sql: Parse MySQL binlog to SQL you want](https://github.com/danfengcao/binlog2sql)