因爲運維、DBA的誤操做或是業務bug,咱們在操做中時不時會出現誤刪除數據狀況。早期要想恢復數據,只能讓業務人員根據線上操做日誌,構造誤刪除的數據,或者DBA使用binlog和備份的方式恢復數據,無論那種,都很是費時費力,並且容易出錯。直到彭立勳首次在MySQL社區爲mysqlbinlog擴展了閃回功能。 html
在美團點評,咱們也遇到過研發人員誤刪主站的配置信息,從而致使主站長達2個小時不可用的狀況。DBA同窗當時使用了技術團隊自研的binlog2sql完成了數據恢復,並屢次挽救了線上誤刪數據致使的嚴重故障。不過,binlog2sql在恢復速度上不盡如人意,所以咱們開發了一個新的工具——MyFlash,它很好地解決了上述痛點,可以方便而且高效地進行數據恢復。mysql
如今該工具正式開源,開源地址爲:github.com/Meituan-Dia… 。git
先來看下目前市面上已有的恢復工具,咱們從實現角度把它們劃分紅以下幾類。github
① mysqlbinlog工具配合sed、awk。該方式先將binlog解析成類SQL的文本,而後使用sed、awk把類SQL文本轉換成真正的SQL。sql
② 給數據庫源碼打patch。該方式擴展了mysqlbinlog的功能,增長Flashback選項。數據庫
③ 使用業界提供的解析binlog的庫,而後進行SQL構造,其優秀表明是binlog2sql。編程
上述幾種實現方式,主要是提供的過濾選項較少,好比不能提供基於SQL類型的過濾,須要回滾一個delete語句,致使在回滾時,須要結合awk、sed等工具進行篩選。緩存
總結了上述幾種工具的優缺點,我認爲理想的閃回工具須要有如下特性。bash
a. 無需把binlog解析成文本,再進行轉換。
b. 提供原生的基於庫、表、SQL類型、位置、時間等多種過濾方式。
c. 支持MySQL多個版本。
d. 對於數據庫的代碼重構不敏感,利於升級。
e. 自主掌控binlog解析,提供儘量靈活的方式。微信
在這些特性中,binlog的解析是一切工做的基礎。接下來我會介紹binlog的基本結構。
一個完整的binlog文件是由一個format description event開頭,一個rotate event結尾,中間由多個其餘event組合而成。
binlog文件實例:
每一個event都是由event header 和event data組成。下面簡單介紹下幾種常見的binlog event。
① formart description event
表達的含義是:
170905 01:59:33 server id 10 end_log_pos 123 CRC32 0xed1ec563
Start: binlog v 4, server v 5.7.18-log created 170905 01:59:33複製代碼
② table map event
表達的含義是:
170905 01:59:33 server id 10 end_log_pos 339 CRC32 0x3de40c0d
Table_map: `test`.`test4` mapped to number 238複製代碼
③ update row event
表達的含義是:
170905 01:59:33 server id 10 end_log_pos 385 CRC32 0x179ef6dd
Update_rows: table id 238 flags: STMT_END_F
UPDATE `test`.`test4` WHERE @1=3 SET @1=13;複製代碼
根據上面的binlog介紹,能夠看到每一個binlog event中event header有個type_code,其中insert爲30,update爲31,delete爲32。對於insert和delete兩個相反的操做,只需把type_code互換,則在binlog event級別完成回滾。
而對於update操做,其格式以下。
其中,BI是指before image,AI是指after image。
咱們只需依次遍歷修改前的數據和修改後的數據,並一一互換便可。所以整個回滾操做的難點在於回滾update語句,而update語句回滾的核心在於計算出每一個AI、BI的長度。下面介紹下長度以及部分字段的計算方法。
鏡像是由一個個字段組成的,根據字段類型的不一樣,其計算長度的方法也不同。
只與字段類型相關。好比int佔用4個字節,bingint佔用8個字節。其中類型信息能夠從table map event中獲取。
與字段類型及其參數相關。好比decimal(18,9),佔用9個字節,參數信息在table map event中。
與字段類型、參數以及實際存儲的值相關。好比varchar(10),有1個字節表示長度,以後的字節才表示真正的數據。好比varchar(280),有2個字節表示長度。實際的長度和數據在一塊兒。
① length encoded integer
binlog中一個或者多個字節組合,分別表示了不一樣的含義。好比,timestamp是由固定的4個字節組成,event類型由一個字節表示;數據庫名和表名最長爲64個字符,即便每一個字符佔用3個字節,那麼佔用的字節數爲192<255。所以最多使用一個字節,就能夠完成實際長度表示。
然而列的實際數量,可能須要超過1個字節、2個字節、3個字節甚至8個字節去表示。若是咱們使用最大的8個字節去表示,那麼在絕大多數狀況下都是浪費存儲空間的。針對這種狀況,length encoded integer應運而生。
好比在獲取一個varchar類型的長度時,首先讀取第一個字節,若是值小於251,那麼varchar的長度就是第一個字節表示的長度。若是第一個字節的值爲0xFC,那麼varchar的長度是由該字節以後的後兩個字節組成,以此類推。
② decimal類型
decimal是由整數部分和小數部分組成。不管是整數仍是小數,每9個數字,須要4個字節。若是不是9的倍數,剩餘的小數位,須要的字節數以下,爲方便描述,將該關係定義爲函數Fnum。
舉例,對於 decimal(18,10):
在上面的章節中,介紹了單個binlog event的反轉方法。在實踐中,咱們每每須要把某個binlog,按照指定的條件,過濾出須要的binlog,並進行反轉。那麼MyFlash是如何完成這些目標的呢?
首先把binlog文件,解析成多個event,放入到相關隊列中。在實現上,爲了儘量加快解析速度,可讓用戶指定解析的開始與結束位置。把binlog文件解析成binlog event後,再判斷下是否符合指定的時間條件,若不符合,則丟棄該event。
注意:用戶能夠不指定位置和時間,則解析整個文件。若是隻指定時間,那麼也須要從文件開始處解析,取出時間信息,再進行判斷。所以,當須要回滾的binlog只佔整個binlog的一小部分時,推薦使用指定位置。
把binlog event組成最小執行單元。在常見的binlog event中table_map event包含了所要了表名、庫名等元數據信息,而row_event(包含write_event、delete_event、update_event)包含了真正的數據。所以在設計中使用了一個最小執行單元概念。所謂的最小執行單元,即least execution event unit,一般包含一個table_map event和若干個row_event。
好比在binlog格式概覽一節中,介紹了table_map_event和update_row_event。若是隻有update_row_event,那麼咱們沒法知道這個event對應的行記錄變動對應的表。所以一個完整的最小執行單元最少包含一個table_map_event和write_row_event、update_row_even、delete_row_event中的一個。
爲何咱們須要使用最小執行單元?由於咱們在閃回操做時,不能簡單的把每一個event反轉以後,而後再將全部event的順序反轉過來。若是這樣的話,就會出現table_map event在row event以後,這顯然是違反binlog執行邏輯的。
有了最小執行單元以後,只需兩步,便可完成反轉。
a. 反轉最小執行單元中的row event。
b. 逆序最小執行單元隊列,便可。
固然在反轉前,也能夠增長過濾操做。好比過濾庫名、表名和SQL類型等。
有了逆序的最小執行單元隊列後,只需把每一個最小執行單元依次輸入到文件便可。不過不要忘了修改每一個binlog event裏的next_position,用來表示下一個binlog的位置。
使用testFlashback2,插入100萬條數據:
CREATE TABLE `testFlashback2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`nameShort` varchar(20) DEFAULT NULL,
`nameLong` varchar(260) DEFAULT NULL,
`amount` decimal(19,9) DEFAULT NULL,
`amountFloat` float DEFAULT NULL,
`amountDouble` double DEFAULT NULL,
`createDatetime6` datetime(6) DEFAULT NULL,
`createDatetime` datetime DEFAULT NULL,
`createTimestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`nameText` text,
`nameBlob` blob,
`nameMedium` mediumtext,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
mysql> select count(*) from testFlashback2;
+----------+
| count(*) |
+----------+
| 1048576 |
+----------+
1 row in set (0.16 sec)
delete from testFlashback2;複製代碼
從上述圖表中能夠看出,MyFlash的速度最快。
美團點評DBA團隊招聘各種DBA人才,base北京上海都可。咱們致力於爲公司提供穩定、可靠、高效的在線存儲服務,打造業界領先的數據庫團隊。這裏有基於Redis Cluster構建的大規模分佈式緩存系統Squirrel,也有基於Tair進行大刀闊斧改進的分佈式KV存儲系統Cellar,還有數千各種架構的MySQL實例,天天提供萬億級的OLTP訪問請求。真正的海量、分佈式、高併發環境。歡迎各位朋友推薦或自薦至jinlong.cai#dianping.com。
回答「思考題」、發現文章有錯誤、對內容有疑問,均可以來微信公衆號(美團點評技術團隊)後臺給咱們留言。咱們每週會挑選出一位「優秀回答者」,贈送一份精美的小禮品。快來掃碼關注咱們吧!
公衆號二維碼