DBA或開發(fā)人員,有時(shí)會(huì)誤刪或者誤更新數(shù)據(jù),如果是線上環(huán)境并且影響較大,就需要能快速回滾。傳統(tǒng)恢復(fù)方法是利用備份重搭實(shí)例,再應(yīng)用去除錯(cuò)誤sql后的binlog來(lái)恢復(fù)數(shù)據(jù)。此法費(fèi)時(shí)費(fèi)力,甚至需要停機(jī)維護(hù),并不適合快速回滾。也有團(tuán)隊(duì)利用LVM快照來(lái)縮短恢復(fù)時(shí)間,但快照的缺點(diǎn)是會(huì)影響mysql的性能。
MySQL閃回(flashback)利用binlog直接進(jìn)行回滾,能快速恢復(fù)且不用停機(jī)。本文將介紹閃回原理,給出筆者的實(shí)戰(zhàn)經(jīng)驗(yàn),并對(duì)現(xiàn)存的閃回工具作比較。
開胃菜
某天,小明因種種原因,誤刪了大批線上用戶表的數(shù)據(jù)。他急忙找到公司DBA請(qǐng)求幫助,“客服電話已被打爆,大量用戶投訴無(wú)法登陸,領(lǐng)導(dǎo)非常惱火。請(qǐng)問多久能恢復(fù)數(shù)據(jù)?”DBA一臉懵逼,沉默十秒后,伸出一根手指?!澳愕囊馑际且环昼娋湍芑謴?fù)?太好了?!毙∶鹘K于有些放松,露出了一絲笑容?!安?,我們中有個(gè)人將會(huì)離開公司?!盌BA沉痛的說道。
勿讓悲劇發(fā)生,盡早將此文轉(zhuǎn)給公司DBA。
閃回原理
binlog概述
MySQL binlog以event的形式,記錄了MySQL server從啟用binlog以來(lái)所有的變更信息,能夠幫助重現(xiàn)這之間的所有變化。MySQL引入binlog主要有兩個(gè)目的:一是為了主從復(fù)制;二是某些備份還原操作后需要重新應(yīng)用binlog。
有三種可選的binlog格式,各有優(yōu)缺點(diǎn):
- statement:基于SQL語(yǔ)句的模式,binlog數(shù)據(jù)量小,但是某些語(yǔ)句和函數(shù)在復(fù)制過程可能導(dǎo)致數(shù)據(jù)不一致甚至出錯(cuò);
- row:基于行的模式,記錄的是行的完整變化。很安全,但是binlog會(huì)比其他兩種模式大很多;
- mixed:混合模式,根據(jù)語(yǔ)句來(lái)選用是statement還是row模式;
利用binlog閃回,需要將binlog格式設(shè)置為row。row模式下,一條使用innodb的insert會(huì)產(chǎn)生如下格式的binlog:
# 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,從后往前回滾回去即可。
對(duì)于單個(gè)event的回滾,我們以表test.user來(lái)演示原理
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
對(duì)于delete操作,我們從binlog提取出delete信息,生成的回滾語(yǔ)句是insert。(注:為了方便解釋,我們用binlog2sql將原始binlog轉(zhuǎn)化成了可讀SQL)
原始:DELETE FROM `test`.`user` WHERE `id`=1 AND `name`='小趙';
回滾:INSERT INTO `test`.`user`(`id`, `name`) VALUES (1, '小趙');
對(duì)于insert操作,回滾SQL是delete。
原始:INSERT INTO `test`.`user`(`id`, `name`) VALUES (2, '小錢');
回滾:DELETE FROM `test`.`user` WHERE `id`=2 AND `name`='小錢';
對(duì)于update操作,回滾sql應(yīng)該交換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`='小李';
閃回實(shí)戰(zhàn)
真實(shí)的閃回場(chǎng)景中,最關(guān)鍵的是能快速篩選出真正需要回滾的SQL。
我們使用開源工具binlog2sql來(lái)進(jìn)行實(shí)戰(zhàn)演練。binlog2sql由美團(tuán)點(diǎn)評(píng)DBA團(tuán)隊(duì)(上海)出品,多次在線上環(huán)境做快速回滾。
首先我們安裝binlog2sql:
shell> git clone https://github.com/danfengcao/binlog2sql.git cd binlog2sql
shell> pip install -r requirements.txt
背景:小明在11:44時(shí)誤刪了test庫(kù)user表大批的數(shù)據(jù),需要緊急回滾。
test庫(kù)user表原有數(shù)據(jù)
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時(shí),user表大批數(shù)據(jù)被誤刪除。與此同時(shí),正常業(yè)務(wù)數(shù)據(jù)是在繼續(xù)寫入的
mysql> delete from user where addtime>'2014-01-01';
Query OK, 16128 rows affected (0.18 sec)
mysql> select count(*) from user;
+----------+
| count(*) |
+----------+
| 261 |
+----------+
恢復(fù)數(shù)據(jù)步驟:
登錄mysql,查看目前的binlog文件
mysql> show master logs;
+------------------+-----------+
| Log_name | File_size |
+------------------+-----------+
| mysql-bin.000053 | 168652863 |
| mysql-bin.000054 | 504549 |
+------------------+-----------+
最新的binlog文件是mysql-bin.000054。我們的目標(biāo)是篩選出需要回滾的SQL,由于誤操作人只知道大致的誤操作時(shí)間,我們首先根據(jù)時(shí)間做一次過濾。只需要解析test庫(kù)user表。(注:如果有多個(gè)sql誤操作,則生成的binlog可能分布在多個(gè)文件,需解析多個(gè)文件)
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
...
根據(jù)位置信息,我們確定了誤操作sql來(lái)自同一個(gè)事務(wù),準(zhǔn)確位置在257427-504272之間(binlog2sql對(duì)于同一個(gè)事務(wù)會(huì)輸出同樣的start position)。再根據(jù)位置過濾,使用 -B 選項(xiàng)生成回滾sql,檢查回滾sql是否正確。(注:真實(shí)場(chǎng)景下,生成的回滾SQL經(jīng)常會(huì)需要進(jìn)一步篩選。結(jié)合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
與業(yè)務(wù)方確認(rèn)回滾sql沒問題,執(zhí)行回滾語(yǔ)句。登錄mysql,確認(rèn)回滾成功。
shell> mysql -h127.0.0.1 -P3306 -uadmin -p'admin' /tmp/rollback.sql
mysql> select count(*) from user;
+----------+
| count(*) |
+----------+
| 16389 |
+----------+
TIPS
- 閃回的關(guān)鍵是快速篩選出真正需要回滾的SQL。
- 先根據(jù)庫(kù)、表、時(shí)間做一次過濾,再根據(jù)位置做更準(zhǔn)確的過濾。
- 由于數(shù)據(jù)一直在寫入,要確?;貪Lsql中不包含其他數(shù)據(jù)??筛鶕?jù)是否是同一事務(wù)、誤操作行數(shù)、字段值的特征等等來(lái)幫助判斷。
- 執(zhí)行回滾sql時(shí)如有報(bào)錯(cuò),需要查實(shí)具體原因,一般是因?yàn)閷?duì)應(yīng)的數(shù)據(jù)已發(fā)生變化。由于是嚴(yán)格的行模式,只要有唯一鍵(包括主鍵)存在,就只會(huì)報(bào)某條數(shù)據(jù)不存在的錯(cuò),不必?fù)?dān)心會(huì)更新不該操作的數(shù)據(jù)。
- 如果待回滾的表與其他表有關(guān)聯(lián),要與開發(fā)說明回滾和不回滾各自的副作用,再確定方案。
- 回滾后數(shù)據(jù)變化,可能對(duì)用戶和線上應(yīng)用造成困惑(類似幻讀)。
再重復(fù)下最重要的兩點(diǎn):篩選出正確SQL!溝通清楚!
閃回工具
MySQL閃回特性最早由阿里彭立勛開發(fā),彭在2012年給官方提交了一個(gè)patch,并對(duì)閃回設(shè)計(jì)思路做了說明(設(shè)計(jì)思路很有啟發(fā)性,強(qiáng)烈推薦閱讀)。但是因?yàn)榉N種原因,業(yè)內(nèi)安裝這個(gè)patch的團(tuán)隊(duì)至今還是少數(shù),真正應(yīng)用到線上的更是少之又少。彭之后,又有多位人員針對(duì)不同mysql版本不同語(yǔ)言開發(fā)了閃回工具,原理用的都是彭的思路。
我將這些閃回工具按實(shí)現(xiàn)方式分成了三類。
第一類是以patch形式集成到官方工具mysqlbinlog中。以彭提交的patch為代表。
優(yōu)點(diǎn)
上手成本低。mysqlbinlog原有的選項(xiàng)都能直接利用,只是多加了一個(gè)閃回選項(xiàng)。閃回特性未來(lái)有可能被官方收錄。
支持離線解析。
缺點(diǎn)
- 兼容性差、項(xiàng)目活躍度不高。由于binlog格式的變動(dòng),如果閃回工具作者不及時(shí)對(duì)補(bǔ)丁升級(jí),則閃回工具將無(wú)法使用。目前已有多位人員分別針對(duì)mysql5.5,5.6,5.7開發(fā)了patch,部分項(xiàng)目代碼公開,但總體上活躍度都不高。
- 難以添加新功能,實(shí)戰(zhàn)效果欠佳。在實(shí)戰(zhàn)中,經(jīng)常會(huì)遇到現(xiàn)有patch不滿足需求的情況,比如要加個(gè)表過濾,很簡(jiǎn)單的一個(gè)需求,代碼改動(dòng)也不會(huì)大,但對(duì)大部分DBA來(lái)說,改mysql源碼還是很困難的事。
- 安裝稍顯麻煩。需要對(duì)mysql源碼打補(bǔ)丁再編譯生成。
這些缺點(diǎn),可能都是閃回沒有流行開來(lái)的原因。
第二類是獨(dú)立工具,通過偽裝成slave拉取binlog來(lái)進(jìn)行處理。以binlog2sql為代表。
優(yōu)點(diǎn)
- 兼容性好。偽裝成slave拉binlog這項(xiàng)技術(shù)在業(yè)界應(yīng)用的非常廣泛,多個(gè)開發(fā)語(yǔ)言都有這樣的活躍項(xiàng)目,MySQL版本的兼容性由這些項(xiàng)目搞定,閃回工具的兼容問題不再突出。
- 添加新功能的難度小。更容易被改造成DBA自己喜歡的形式。更適合實(shí)戰(zhàn)。
- 安裝和使用簡(jiǎn)單。
缺點(diǎn)
必須開啟MySQL server。
第三類是簡(jiǎn)單腳本。先用mysqlbinlog解析出文本格式的binlog,再根據(jù)回滾原理用正則進(jìn)行匹配并替換。
優(yōu)點(diǎn)
- 腳本寫起來(lái)方便,往往能快速搞定某個(gè)特定問題。
- 安裝和使用簡(jiǎn)單。
- 支持離線解析。
缺點(diǎn)
就目前的閃回工具而言,線上環(huán)境的閃回,筆者建議使用binlog2sql,離線解析使用mysqlbinlog。
關(guān)于DDL的flashback
本文所述的flashback僅針對(duì)DML語(yǔ)句的快速回滾。但如果誤操作是DDL的話,是無(wú)法利用binlog做快速回滾的,因?yàn)榧词乖趓ow模式下,binlog對(duì)于DDL操作也不會(huì)記錄每行數(shù)據(jù)的變化。要實(shí)現(xiàn)DDL快速回滾,必須修改MySQL源碼,使得在執(zhí)行DDL前先備份老數(shù)據(jù)。目前有多個(gè)mysql定制版本實(shí)現(xiàn)了DDL閃回特性,阿里林曉斌團(tuán)隊(duì)提交了patch給MySQL官方,MariaDB預(yù)計(jì)在不久后加入包含DDL的flashback特性。DDL閃回的副作用是會(huì)增加額外存儲(chǔ)??紤]到其應(yīng)用頻次實(shí)在過低,本文不做詳述,有興趣的同學(xué)可以自己去了解,重要的幾篇文章我在參考資料中做了引用。
有任何問題,或有mysql閃回相關(guān)的優(yōu)秀工具優(yōu)秀文章遺漏,煩請(qǐng)告知。
您可能感興趣的文章:- 在MySQL中使用mysqlbinlog flashback的簡(jiǎn)單教程
- Oracle 11g 新特性 Flashback Data Archive 使用實(shí)例
- oracle中誤刪除表后恢復(fù)語(yǔ)句(FLASHBACK)