誤刪數據後除了跑路,還能怎麼辦

31講誤刪數據後除了跑路,還能怎麼辦

今天我要和你討論的是一個沉重的話題:誤刪數據。

在前面幾篇文章中,我們介紹了MySQL的高可用架構。當然,傳統的高可用架構是不能預防誤刪數據的,因爲主庫的一個drop table命令,會通過binlog傳給所有從庫和級聯從庫,進而導致整個集羣的實例都會執行這個命令。

雖然我們之前遇到的大多數的數據被刪,都是運維同學或者DBA背鍋的。但實際上,只要有數據操作權限的同學,都有可能踩到誤刪數據這條線。

今天我們就來聊聊誤刪數據前後,我們可以做些什麼,減少誤刪數據的風險,和由誤刪數據帶來的損失。

爲了找到解決誤刪數據的更高效的方法,我們需要先對和MySQL相關的誤刪數據,做下分類:

  1. 使用delete語句誤刪數據行;

  2. 使用drop table或者truncate table語句誤刪數據表;

  3. 使用drop database語句誤刪數據庫;

  4. 使用rm命令誤刪整個MySQL實例。

誤刪行

第24篇文章中,我們提到如果是使用delete語句誤刪了數據行,可以用Flashback工具通過閃回把數據恢復回來。

Flashback恢復數據的原理,是修改binlog的內容,拿回原庫重放。而能夠使用這個方案的前提是,需要確保binlog_format=row 和 binlog_row_image=FULL。

具體恢復數據時,對單個事務做如下處理:

  1. 對於insert語句,對應的binlog event類型是Write_rows event,把它改成Delete_rows event即可;

  2. 同理,對於delete語句,也是將Delete_rows event改爲Write_rows event;

  3. 而如果是Update_rows的話,binlog裏面記錄了數據行修改前和修改後的值,對調這兩行的位置即可。

如果誤操作不是一個,而是多個,會怎麼樣呢?比如下面三個事務:

(A)delete ...
(B)insert ...
(C)update ...

現在要把數據庫恢復回這三個事務操作之前的狀態,用Flashback工具解析binlog後,寫回主庫的命令是:

(reverse C)update ...
(reverse B)delete ...
(reverse A)insert ...

也就是說,如果誤刪數據涉及到了多個事務的話,需要將事務的順序調過來再執行。

需要說明的是,我不建議你直接在主庫上執行這些操作。

恢復數據比較安全的做法,是恢復出一個備份,或者找一個從庫作爲臨時庫,在這個臨時庫上執行這些操作,然後再將確認過的臨時庫的數據,恢復回主庫。

爲什麼要這麼做呢?

這是因爲,一個在執行線上邏輯的主庫,數據狀態的變更往往是有關聯的。可能由於發現數據問題的時間晚了一點兒,就導致已經在之前誤操作的基礎上,業務代碼邏輯又繼續修改了其他數據。所以,如果這時候單獨恢復這幾行數據,而又未經確認的話,就可能會出現對數據的二次破壞。

當然,我們不止要說誤刪數據的事後處理辦法,更重要是要做到事前預防。我有以下兩個建議:

  1. 把sql_safe_updates參數設置爲on。這樣一來,如果我們忘記在delete或者update語句中寫where條件,或者where條件裏面沒有包含索引字段的話,這條語句的執行就會報錯。

  2. 代碼上線前,必須經過SQL審計。

你可能會說,設置了sql_safe_updates=on,如果我真的要把一個小表的數據全部刪掉,應該怎麼辦呢?

如果你確定這個刪除操作沒問題的話,可以在delete語句中加上where條件,比如where id>=0。

但是,delete全表是很慢的,需要生成回滾日誌、寫redo、寫binlog。所以,從性能角度考慮,你應該優先考慮使用truncate table或者drop table命令。

使用delete命令刪除的數據,你還可以用Flashback來恢復。而使用truncate /drop table和drop database命令刪除的數據,就沒辦法通過Flashback來恢復了。爲什麼呢?

這是因爲,即使我們配置了binlog_format=row,執行這三個命令時,記錄的binlog還是statement格式。binlog裏面就只有一個truncate/drop 語句,這些信息是恢復不出數據的。

那麼,如果我們真的是使用這幾條命令誤刪數據了,又該怎麼辦呢?

誤刪庫/表

這種情況下,要想恢復數據,就需要使用全量備份,加增量日誌的方式了。這個方案要求線上有定期的全量備份,並且實時備份binlog。

在這兩個條件都具備的情況下,假如有人中午12點誤刪了一個庫,恢復數據的流程如下:

  1. 取最近一次全量備份,假設這個庫是一天一備,上次備份是當天0點;

  2. 用備份恢復出一個臨時庫;

  3. 從日誌備份裏面,取出凌晨0點之後的日誌;

  4. 把這些日誌,除了誤刪除數據的語句外,全部應用到臨時庫。

這個流程的示意圖如下所示:

圖1 數據恢復流程-mysqlbinlog方法

關於這個過程,我需要和你說明如下幾點:

  1. 爲了加速數據恢復,如果這個臨時庫上有多個數據庫,你可以在使用mysqlbinlog命令時,加上一個–database參數,用來指定誤刪表所在的庫。這樣,就避免了在恢復數據時還要應用其他庫日誌的情況。

  2. 在應用日誌的時候,需要跳過12點誤操作的那個語句的binlog:

    • 如果原實例沒有使用GTID模式,只能在應用到包含12點的binlog文件的時候,先用–stop-position參數執行到誤操作之前的日誌,然後再用–start-position從誤操作之後的日誌繼續執行;
    • 如果實例使用了GTID模式,就方便多了。假設誤操作命令的GTID是gtid1,那麼只需要執行set gtid_next=gtid1;begin;commit; 先把這個GTID加到臨時實例的GTID集合,之後按順序執行binlog的時候,就會自動跳過誤操作的語句。

不過,即使這樣,使用mysqlbinlog方法恢復數據還是不夠快,主要原因有兩個:

  1. 如果是誤刪表,最好就是隻恢復出這張表,也就是隻重放這張表的操作,但是mysqlbinlog工具並不能指定只解析一個表的日誌;

  2. 用mysqlbinlog解析出日誌應用,應用日誌的過程就只能是單線程。我們在第26篇文章中介紹的那些並行複製的方法,在這裏都用不上。

一種加速的方法是,在用備份恢復出臨時實例之後,將這個臨時實例設置成線上備庫的從庫,這樣:

  1. 在start slave之前,先通過執行
    change replication filter replicate_do_table = (tbl_name) 命令,就可以讓臨時庫只同步誤操作的表;

  2. 這樣做也可以用上並行複製技術,來加速整個數據恢復過程。

這個過程的示意圖如下所示。

圖2 數據恢復流程-master-slave方法

可以看到,圖中binlog備份系統到線上備庫有一條虛線,是指如果由於時間太久,備庫上已經刪除了臨時實例需要的binlog的話,我們可以從binlog備份系統中找到需要的binlog,再放回備庫中。

假設,我們發現當前臨時實例需要的binlog是從master.000005開始的,但是在備庫上執行show binlogs 顯示的最小的binlog文件是master.000007,意味着少了兩個binlog文件。這時,我們就需要去binlog備份系統中找到這兩個文件。

把之前刪掉的binlog放回備庫的操作步驟,是這樣的:

  1. 從備份系統下載master.000005和master.000006這兩個文件,放到備庫的日誌目錄下;

  2. 打開日誌目錄下的master.index文件,在文件開頭加入兩行,內容分別是 “./master.000005”和“./master.000006”;

  3. 重啓備庫,目的是要讓備庫重新識別這兩個日誌文件;

  4. 現在這個備庫上就有了臨時庫需要的所有binlog了,建立主備關係,就可以正常同步了。

不論是把mysqlbinlog工具解析出的binlog文件應用到臨時庫,還是把臨時庫接到備庫上,這兩個方案的共同點是:誤刪庫或者表後,恢復數據的思路主要就是通過備份,再加上應用binlog的方式。

也就是說,這兩個方案都要求備份系統定期備份全量日誌,而且需要確保binlog在被從本地刪除之前已經做了備份。

但是,一個系統不可能備份無限的日誌,你還需要根據成本和磁盤空間資源,設定一個日誌保留的天數。如果你的DBA團隊告訴你,可以保證把某個實例恢復到半個月內的任意時間點,這就表示備份系統保留的日誌時間就至少是半個月。

另外,我建議你不論使用上述哪種方式,都要把這個數據恢復功能做成自動化工具,並且經常拿出來演練。爲什麼這麼說呢?

這裏的原因,主要包括兩個方面:

  1. 雖然“發生這種事,大家都不想的”,但是萬一出現了誤刪事件,能夠快速恢復數據,將損失降到最小,也應該不用跑路了。

  2. 而如果臨時再手忙腳亂地手動操作,最後又誤操作了,對業務造成了二次傷害,那就說不過去了。

延遲複製備庫

雖然我們可以通過利用並行複製來加速恢復數據的過程,但是這個方案仍然存在“恢復時間不可控”的問題。

如果一個庫的備份特別大,或者誤操作的時間距離上一個全量備份的時間較長,比如一週一備的實例,在備份之後的第6天發生誤操作,那就需要恢復6天的日誌,這個恢復時間可能是要按天來計算的。

那麼,我們有什麼方法可以縮短恢復數據需要的時間呢?

如果有非常核心的業務,不允許太長的恢復時間,我們可以考慮搭建延遲複製的備庫。這個功能是MySQL 5.6版本引入的。

一般的主備複製結構存在的問題是,如果主庫上有個表被誤刪了,這個命令很快也會被髮給所有從庫,進而導致所有從庫的數據表也都一起被誤刪了。

延遲複製的備庫是一種特殊的備庫,通過 CHANGE MASTER TO MASTER_DELAY = N命令,可以指定這個備庫持續保持跟主庫有N秒的延遲。

比如你把N設置爲3600,這就代表瞭如果主庫上有數據被誤刪了,並且在1小時內發現了這個誤操作命令,這個命令就還沒有在這個延遲複製的備庫執行。這時候到這個備庫上執行stop slave,再通過之前介紹的方法,跳過誤操作命令,就可以恢復出需要的數據。

這樣的話,你就隨時可以得到一個,只需要最多再追1小時,就可以恢復出數據的臨時實例,也就縮短了整個數據恢復需要的時間。

預防誤刪庫/表的方法

雖然常在河邊走,很難不溼鞋,但終究還是可以找到一些方法來避免的。所以這裏,我也會給你一些減少誤刪操作風險的建議。

第一條建議是,賬號分離。這樣做的目的是,避免寫錯命令。比如:

  • 我們只給業務開發同學DML權限,而不給truncate/drop權限。而如果業務開發人員有DDL需求的話,也可以通過開發管理系統得到支持。
  • 即使是DBA團隊成員,日常也都規定只使用只讀賬號,必要的時候才使用有更新權限的賬號。

第二條建議是,制定操作規範。這樣做的目的,是避免寫錯要刪除的表名。比如:

  • 在刪除數據表之前,必須先對錶做改名操作。然後,觀察一段時間,確保對業務無影響以後再刪除這張表。
  • 改表名的時候,要求給表名加固定的後綴(比如加_to_be_deleted),然後刪除表的動作必須通過管理系統執行。並且,管理系刪除表的時候,只能刪除固定後綴的表。

rm刪除數據

其實,對於一個有高可用機制的MySQL集羣來說,最不怕的就是rm刪除數據了。只要不是惡意地把整個集羣刪除,而只是刪掉了其中某一個節點的數據的話,HA系統就會開始工作,選出一個新的主庫,從而保證整個集羣的正常工作。

這時,你要做的就是在這個節點上把數據恢復回來,再接入整個集羣。

當然了,現在不止是DBA有自動化系統,SA(系統管理員)也有自動化系統,所以也許一個批量下線機器的操作,會讓你整個MySQL集羣的所有節點都全軍覆沒。

應對這種情況,我的建議只能是說盡量把你的備份跨機房,或者最好是跨城市保存。

小結

今天,我和你討論了誤刪數據的幾種可能,以及誤刪後的處理方法。

但,我要強調的是,預防遠比處理的意義來得大。

另外,在MySQL的集羣方案中,會時不時地用到備份來恢復實例,因此定期檢查備份的有效性也很有必要。

如果你是業務開發同學,你可以用show grants命令查看賬戶的權限,如果權限過大,可以建議DBA同學給你分配權限低一些的賬號;你也可以評估業務的重要性,和DBA商量備份的週期、是否有必要創建延遲複製的備庫等等。

數據和服務的可靠性不止是運維團隊的工作,最終是各個環節一起保障的結果。

今天的課後話題是,回憶下你親身經歷過的誤刪數據事件吧,你用了什麼方法來恢復數據呢?你在這個過程中得到的經驗又是什麼呢?

你可以把你的經歷和經驗寫在留言區,我會在下一篇文章的末尾選取有趣的評論和你一起討論。感謝你的收聽,也歡迎你把這篇文章分享給更多的朋友一起閱讀。

上期問題時間

我在上一篇文章給你留的問題,是關於空表的間隙的定義。

一個空表就只有一個間隙。比如,在空表上執行:

begin;
select * from t where id>1 for update;

這個查詢語句加鎖的範圍就是next-key lock (-∞, supremum]。

驗證方法的話,你可以使用下面的操作序列。你可以在圖4中看到顯示的結果。

圖3 復現空表的next-key lock

圖4 show engine innodb status 部分結果

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章