今天我要和你討論的是一個沉重的話題:誤刪數據。mysql
在前面幾篇文章中,咱們介紹了 MySQL 的高可用架構。固然,傳統的高可用架構是不能預防誤刪數據的,由於主庫的一個 drop table 命令,會經過 binlog 傳給全部從庫和級聯
從庫,進而致使整個集羣的實例都會執行這個命令。sql
雖然咱們以前遇到的大多數的數據被刪,都是運維同窗或者 DBA 背鍋的。但實際上,只要有數據操做權限的同窗,都有可能踩到誤刪數據這條線。數據庫
今天咱們就來聊聊誤刪數據先後,咱們能夠作些什麼,減小誤刪數據的風險,和由誤刪數據帶來的損失。安全
爲了找到解決誤刪數據的更高效的方法,咱們須要先對和 MySQL 相關的誤刪數據,作下分類:bash
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 裏面記錄了數據行修改前和修改後的值,對調這兩行的位置便可。
若是誤操做不是一個,而是多個,會怎麼樣呢?好比下面三個事務:spa
(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,若是我真的要把一個小表的數據所有刪掉,應該怎麼辦呢?
你可能會說,設置了 sql_safe_updates=on,若是我真的要把一個小表的數據所有刪掉,應該怎麼辦呢?
若是你肯定這個刪除操做沒問題的話,能夠在 delete 語句中加上 where 條件,好比where id>=0。
可是,delete 全表是很慢的,須要生成回滾日誌、寫 redo、寫 binlog。因此,從性能角度考慮,你應該優先考慮使用 truncate table 或者 drop table 命令。
二、delete、truncate table、drop table的區別
使用 delete 命令刪除的數據,你還能夠用 Flashback 來恢復。而使用 truncate /droptable 和 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),而後刪除表的動做必須經過管理系統執行。而且,管理系刪除表的時候,只能刪除固定後綴的表。
其實,對於一個有高可用機制的 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 部分結果
@老楊同志 給出了正確的分析和 SQL 語句驗證方法;@庫淘淘 指出了 show engine innodb status 驗證結論。