數據庫沒有備份,沒有使用Binlog的情況下,如何恢復數據?極客時間學習筆記


MySQL 的復制主要是通過 Binlog 來完成的,Binlog 記錄了數據庫更新的事件,從庫 I/O 線程會向主庫發送 Binlog 更新的請求,同時主庫二進制轉儲線程會發送 Binlog 給從庫作為中繼日志進行保存,然后從庫會通過中繼日志重放,完成數據庫的同步更新。這種同步操作是近乎實時的同步,然而也有人為誤操作情況的發生,比如 DBA 人員為了方便直接在生產環境中對數據進行操作,或者忘記了當前是在開發環境,還是在生產環境中,就直接對數據庫進行操作,這樣很有可能會造成數據的丟失,情況嚴重時,誤操作還有可能同步給從庫實時更新。不過我們依然有一些策略可以防止這種誤操作,比如利用延遲備份的機制。延遲備份最大的作用就是避免這種“手抖”的情況,讓我們在延遲從庫進行誤操作前停止下來,進行數據庫的恢復。

當然如果我們對數據庫做過時間點備份,也可以直接恢復到該時間點。不過我們今天要討論的是一個特殊的情況,也就是在沒做數據庫備份,沒有開啟使用 Binlog 的情況下,盡可能地找回數據。

今天的內容主要包括以下幾個部分:

  1. InnoDB 存儲引擎中的表空間是怎樣的?兩種表空間存儲方式各有哪些優缺點?
  2. 如果.ibd 文件損壞了,數據該如何找回?
  3. 如何模擬 InnoDB 文件的損壞與數據恢復?

InnoDB 存儲引擎的表空間

InnoDB 存儲引擎的文件格式是.ibd 文件,數據會按照表空間(tablespace)進行存儲,分為共享表空間和獨立表空間。如果想要查看表空間的存儲方式,我們可以對innodb_file_per_table 變量進行查詢,使用 show variables like 'innodb_file_per_table'; 。ON 表示獨立表空間,而 OFF 則表示共享表空間。

 

如果采用共享表空間的模式,InnoDB 存儲的表數據都會放到共享表空間中,也就是多個數據表共用一個表空間,同時表空間也會自動分成多個文件存放到磁盤上。這樣做的好處在於單個數據表的大小可以突破文件系統大小的限制,最大可以達到 64TB,也就是 InnoDB 存儲引擎表空間的上限。不足也很明顯,多個數據表存放到一起,結構不清晰,不利於數據的找回,同時將所有數據和索引都存放到一個文件中,也會使得共享表空間的文件很大。

采用獨立表空間的方式可以讓每個數據表都有自己的物理文件,也就是 table_name.ibd 的文件,在這個文件中保存了數據表中的數據、索引、表的內部數據字典等信息。它的優勢在於每張表都相互獨立,不會影響到其他數據表,存儲結構清晰,利於數據恢復,同時數據表還可以在不同的數據庫之間進行遷移。

如果.ibd 文件損壞了,數據如何找回

如果我們之前沒有做過全量備份,也沒有開啟 Binlog,那么我們還可以通過.ibd 文件進行數據恢復,采用獨立表空間的方式可以很方便地對數據庫進行遷移和分析。如果我們誤刪除(DELETE)某個數據表或者某些數據行,也可以采用第三方工具回數據。

我們這里可以使用 Percona Data Recovery Tool for InnoDB 工具,能使用工具進行修復是因為我們在使用 DELETE 的時候是邏輯刪除。我們之前學習過 InnoDB 的頁結構,在保存數據行的時候還有個刪除標記位,對應的是頁結構中的 delete_mask 屬性,該屬性為 1 的時候標記了記錄已經被邏輯刪除,實際上並不是真的刪除。不過當有新的記錄插入的時候,被刪除的行記錄可能會被覆蓋掉。所以當我們發生了 DELETE 誤刪除的時候,一定要第一時間停止對誤刪除的表進行更新和寫入,及時將.ibd 文件拷貝出來並進行修復。

如果已經開啟了 Binlog,就可以使用閃回工具,比如 mysqlbinlog 或者 binlog2sql,從工具名稱中也能看出來它們都是基於 Binlog 來做的閃回。原理就是因為 Binlog 文件本身保存了數據庫更新的事件(Event),通過這些事件可以幫我們重現數據庫的所有更新變化,也就是 Binlog 回滾。

下面我們就來看下沒有做過備份,也沒有開啟 Binlog 的情況下,如果.ibd 文件發生了損壞,如何通過數據庫自身的機制來進行數據恢復。

實際上,InnoDB 是有自動恢復機制的,如果發生了意外,InnoDB 可以在讀取數據表時自動修復錯誤。但有時候.ibd 文件損壞了,會導致數據庫無法正常讀取數據表,這時我們就需要人工介入,調整一個參數,這個參數叫做 innodb_force_recovery 

我們可以通過命令 show variables like 'innodb_force_recovery'; 來查看當前參數的狀態,你能看到默認為 0,表示不進行強制恢復。如果遇到錯誤,比如 ibd 文件中的數據頁發生損壞,則無法讀取數據,會發生 MySQL 宕機的情況,此時會將錯誤日志記錄下來。


innodb_force_recovery 參數一共有 7 種狀態,除了默認的 0 以外,還可以為 1-6 的取值,分別代表不同的強制恢復措施。

當我們需要強制恢復的時候,可以將 innodb_force_recovery 設置為 1,表示即使發現了損壞頁也可以繼續讓服務運行,這樣我們就可以讀取數據表,並且對當前損壞的數據表進行分析和備份。

通常 innodb_force_recovery 參數設置為 1,只要能正常讀取數據表即可。但如果參數設置為 1 之后還無法讀取數據表,我們可以將參數逐一增加,比如 2、3 等。一般來說不需要將參數設置到 4 或以上,因為這有可能對數據文件造成永久破壞。另外當 innodb_force_recovery設置為大於 0 時,相當於對 InnoDB 進行了寫保護,只能進行 SELECT 讀取操作,還是有限制的讀取,對於 WHERE 條件以及 ORDER BY 都無法進行操作。

當我們開啟了強制恢復之后,數據庫的功能會受到很多限制,我們需要盡快把有問題的數據表備份出來,完成數據恢復操作。整體的恢復步驟可以按照下面的思路進行:

1. 使用 innodb_force_recovery 啟動服務器

將 innodb_force_recovery 參數設置為 1,啟動數據庫。如果數據表不能正常讀取,需要調大參數直到能讀取數據為止。通常設置為 1 即可。

2. 備份數據表

在備份數據之前,需要准備一個新的數據表,這里需要使用 MyISAM 存儲引擎。原因很簡單,InnoDB 存儲引擎已經寫保護了,無法將數據備份出來。然后將損壞的 InnoDB 數據表備份到新的 MyISAM 數據表中。

3. 刪除舊表,改名新表

數據備份完成之后,我們可以刪除掉原有損壞的 InnoDB 數據表,然后將新表進行改名。

4. 關閉 innodb_force_recovery ,並重啟數據庫

innodb_force_recovery 大於 1 的時候會有很多限制,我們需要將該功能關閉,然后重啟數據庫,並且將數據表的 MyISAM 存儲引擎更新為 InnoDB 存儲引擎。

InnoDB 文件的損壞與恢復實例

我們剛才說了 InnoDB 文件損壞時的人工操作過程,下面我們用一個例子來模擬下。

生成 InnoDB 數據表

為了簡便,我們創建一個數據表 t1,只有 id 一個字段,類型為 int。使用命令 create table t1(id int); 即可。


然后創建一個存儲過程幫我們生成一些數據:

BEGIN
-- 當前數據行
DECLARE i INT DEFAULT 0;
-- 最大數據行數
DECLARE max_num INT DEFAULT 100;
-- 關閉自動提交
SET autocommit=0;
REPEAT
SET i=i+1;
-- 向 t1 表中插入數據
INSERT INTO t1(id) VALUES(i);
UNTIL i = max_num
END REPEAT;
-- 提交事務
COMMIT;
END

然后我們運行 call insert_t1() ,這個存儲過程幫我們插入了 100 條數據,這樣我們就有了 t1.ibd 這個文件。

模擬損壞.ibd 文件

實際工作中我們可能會遇到各種各樣的情況,比如.ibd 文件損壞等,如果遇到了數據文件的損壞,MySQL 是無法正常讀取的。在模擬損壞.ibd 文件之前,我們需要先關閉掉 MySQL 服務,然后用編輯器打開 t1.ibd,類似下圖所示:


文件是有二進制編碼的,看不懂沒有關系,我們只需要破壞其中的一些內容即可,比如我在 t1.ibd 文件中刪除了 2 行內容(文件大部分內容為 0,我們在文件中間部分找到一些非 0 的取值,然后刪除其中的兩行:4284 行與 4285 行,原 ibd 文件和損壞后的 ibd 文件見 GitHub 地址 。其中 t1.ibd 為創建的原始數據文件,t1- 損壞.ibd 為損壞后的數據文件,你需要自己創建 t1 數據表,然后將 t1- 損壞.ibd 拷貝到本地,並改名為 t1.ibd)。

然后我們保存文件,這時.ibd 文件發生了損壞,如果我們沒有打開 innodb_force_recovery,那么數據文件無法正常讀取。為了能讀取到數據表中的數據,我們需要修改 MySQL 的配置文件,找到 [mysqld] 的位置,然后再下面增加一行 innodb_force_recovery=1 

備份數據表

當我們設置 innodb_force_recovery 參數為 1 的時候,可以讀取到數據表 t1 中的數據,但是數據不全。我們使用 SELECT * FROM t1 LIMIT 10; 讀取當前前 10 條數據。


但是如果我們想要完整的數據,使用 SELECT * FROM t1 LIMIT 100; 就會發生如下錯誤。


這是因為讀取的部分包含了已損壞的數據頁,我們可以采用二分查找判斷數據頁損壞的位置。這里我們通過實驗,可以得出只有最后一個記錄行收到了損壞,而前 99 條記錄都可以正確讀出(具體實驗過程省略)。

這樣我們就能判斷出來有效的數據行的位置,從而將它們備份出來。首先我們創建一個相同的表結構 t2,存儲引擎設置為 MyISAM。我剛才講過這里使用 MyISAM 存儲引擎是因為在innodb_force_recovery=1 的情況下,無法對 innodb 數據表進行寫數據。使用命令CREATE TABLE t2(id int) ENGINE=MyISAM; 

然后我們將數據表 t1 中的前 99 行數據復制給 t2 數據表,使用:

INSERT INTO t2 SELECT * FROM t1 LIMIT 99;

 


我們剛才講過在分析 t1 數據表的時候無法使用 WHERE 以及 ORDER BY 等子句,這里我們可以實驗一下,如果想要查詢 id<10 的數據行都有哪些,那么會發生如下錯誤。原因是損壞的數據頁無法進行條件判斷。

刪除舊表,改名新表

剛才我們已經恢復了大部分的數據。雖然還有一行記錄沒有恢復,但是能找到絕大部分的數據也是好的。然后我們就需要把之前舊的數據表刪除掉,使用 DROP TABLE t1; 


更新表名,將數據表名稱由 t2 改成 t1,使用 RENAME TABLE t2 to t1; 


將新的數據表 t1 存儲引擎改成 InnoDB,不過直接修改的話,會報如下錯誤:

關閉 innodb_force_recovery ,並重啟數據庫

因為上面報錯,所以我們需要將 MySQL 配置文件中的 innodb_force_recovery=1 刪除掉,然后重啟數據庫。最后將 t1 的存儲引擎改成 InnoDB 即可,使用 ALTER TABLE t1 engine = InnoDB; 

總結

我們剛才人工恢復了損壞的 ibd 文件中的數據,雖然沒有 100% 找回,但是相比於束手無措來說,已經是不幸中的萬幸,至少我們還可以把正確的數據頁中的記錄成功備份出來,盡可能恢復原有的數據表。在這個過程中相信你應該對 ibd 文件,以及 InnoDB 自身的強制恢復(Force Recovery)機制有更深的了解。

數據表損壞,以及人為的誤刪除都不是我們想要看到的情況,但是我們不能指望運氣,或者說我們不能祈禱這些事情不會發生。在遇到這些情況的時候,應該通過機制盡量保證數據庫的安全穩定運行。這個過程最主要的就是應該及時備份,並且開啟二進制日志,這樣當有誤操作的時候就可以通過數據庫備份以及 Binlog 日志來完成數據恢復。同時采用延遲備份的策略也可以盡量抵御誤操作。總之,及時備份是非常有必要的措施,同時我們還需要定時驗證備份文件的有效性,保證備份文件可以正常使用。

如果你遇到了數據庫 ibd 文件損壞的情況,並且沒有采用任何的備份策略,可以嘗試使用 InnoDB 的強制恢復機制,啟動 MySQL 並且將損壞的數據表轉儲到 MyISAM 數據表中,盡可能恢復已有的數據。總之機制比人為更靠譜,我們要為長期的運營做好充足的准備。一旦發生了誤操作這種緊急情況,不要慌張,及時采取對應的措施才是最重要的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM