1. 程式人生 > >MySQL如何利用ibd檔案恢復資料?

MySQL如何利用ibd檔案恢復資料?

前言

資料庫丟失之痛

磁碟壞道、斷電等意外不是常態,但遇上了就足夠你“驚心動魄”!

如果是資料庫損壞造成的資料丟失,Binlog也不可用了,怎麼辦?~~

為了在短時間內無損恢復資料以保證業務穩定性,除了利用binlog,我們還修煉了一招新的恢復技能!

正文

還記得我們之前寫過的《只需一招,讓失控的研發愛上你》嗎?前文提到過我們日常使用的比較多的兩種資料庫恢復方法是:

資料庫

以上兩種方法都可以實現實時性的回檔,但是你會認為有了這兩種技能就夠了嗎?

不….!

在線上這種錯綜複雜的架構中,其實還有很多未知的原因,我們是沒法預知的。例如以下這種情況:

因辛勤勞動而折壽的磁碟產生成長壞道,導致資料庫損壞。而又剛好損壞了ibdata檔案和binlog檔案。那麼如果還想著以定時備份+binlog恢復的方案就不可能了,難道只能用定點備份回檔嗎?深思熟慮後,作為一名運維人員,我們是絕對不會在萬不得已的情況下實行有損回檔,因為這對業務產生太大的影響了,但是除此之外又能怎麼辦呢?下面我們將要放一門大招!!!

ibdata

首先檢查資料庫環境,是否開啟了獨立表空間,如果已經開啟的話,那恭喜你,有很大的機會可以恢復全部資料。我們可以依賴每個資料庫目錄下的frm和ibd檔案來實現資料恢復,一般來說如果使用了InnoDB但沒開啟獨立表空間的話,所有的資料庫表資訊和元資料都會寫入ibdata檔案裡,這樣長久執行的話,ibdata檔案會變得越來越大,資料庫效能下降。InnoDB提供了開啟獨立表空間引數,可以讓資料獨立存放起來,這樣子ibdata檔案只用於存放一些引擎相關的索引資訊,實際的資料寫入到獨立的frm和ibd檔案裡。

好,有了frm和ibd檔案,我們可以開始嘗試資料恢復了,他的過程比binlog還原既驚險又有趣!首先我們來看一下關於ibd和frm的說明:

.frm檔案:儲存了每個表的元資料,包括表結構的定義等,該檔案與資料庫引擎無關。

.ibd檔案:InnoDB引擎開啟了獨立表空間(my.ini中配置innodb_file_per_table = 1)產生的存放該表的資料和索引的檔案。

我們都知道,對於InnoDB的資料庫,如果不把整個資料目錄拷貝,只拷貝指定資料庫目錄到新的例項下,資料庫是認不出來的。那麼如何根據這兩個檔案還恢復資料庫呢?

恢復思路:

由於ibdata檔案上存放了一些關於引擎的索引資訊,ibdata檔案損壞導致表名索引丟失而無法啟動。那麼我們可以先把原來舊的整個資料目錄改名備份,然後重新初始化資料庫生成新的ibdata檔案,然後重新建立原有的資料庫以及對應的表,最後把備份的表空間id號改為新建的表空間id號(ibdata檔案裡有每個表唯一的表空間索引id,該id由建立新表的數量依次遞增),這樣就可以恢復原來的資料庫了。

舉個例子:

庫名:test_restore

表結構:db_struc.sql

表文件:G_RESTORE.ibd、G_RESTORE.frm

1. 建立新庫,匯入表結構

#mysql -uroot –p****  -e “create database test_restore”

#mysql -uroot –p****  test_restore < db_struc.sql

2. 檢視並修改test_restore庫中表在新例項中的id

#vim -b /data/database/mysql/test_restore/G_RESTORE.ibd

直接開啟為亂碼,轉成16進位制檢視。Vi中執行  :%!xxd 轉化為16進位制。結果為 :

如圖所示。G_RESTORE表在mysql資料庫中的id為00fe。

修改備份的G_RESTORE.ibd檔案。操作同上,注意需先備份。

#cp G_RESTORE.ibd{,_back}

#vim -v G_RESTORE.ibd

將011b修改為00fe 。注意。修改完成後需要在vim中先執行 :%!xxd  -r

再wq 儲存退出檔案。不然儲存到的是16進位制檢視的結果。

儲存結果如下:

將修改好的G_RESTORE.ibd 替換掉新資料庫中的G_RESTORE.ibd檔案。

關於ibdata表id的解釋:

ibdata

參考官方文件解釋,每個表空間分配了4個位元組儲存了表空間id資訊,最後偏移量地址為38。還有一組預留的表空間id,同樣是4個位元組,最後偏移量地址為42。

3. 驗證並還原mysql資料

關閉mysql。修改my.conf。

innodb_force_recovery=6

innodb_purge_threads=0

啟動資料庫。如果不修改。資料庫會認為G_RESTORE已被損壞。

Select 一下,即可檢視到還原結果,但此時插入資料會報錯,應儘快將資料dump出來 ,導回原來的例項中。

Select

匯出資料,再匯入資料,恢復完畢!

#mysqldump -uroot –p****** test_restore > test_restore.sql

#mysql -uroot –p****** test_restore < test_restore.sql

說明:變更了新的space id後的.ibd表文件,啟動資料庫後只能認出資料,但不能寫入,這是因為原ibdata檔案不僅儲存了space id索引,還同時儲存了一些其它的元資料。為了使元資料補全,所以採取匯出、再匯入的操作。

以上舉例為單個庫表的恢復過程,看到這裡大家一定會產生另一個疑問吧?線上的場景不可能是隻有一個表的,資料庫表很多的情況下,這樣一個個表的修改,速度無疑是太慢了。那麼存在大量表的情況下如何恢復呢?思路是,取得備份的ibd檔案的id值,按id值順序來建表,中間跨度隨便建表語句來湊夠數(每個表空間索引id由建立新表的數量依次遞增)。實現方式如下:

1. 獲取備份資料庫ibd檔案的space id號,並排序。

for ibd in `find test_restore/ -name “*.ibd”` ; do  echo -e “${ibd//\// }   \c” ;hexdump -C ${ibd} |head -n 3 |tail -n 1|awk ‘{print  strtonum(“0x”$6$7)}’ ;done | sort -n  -k 3 | column -t > /tmp/

生成的ibd.txt檔案,格式如下:(庫名–表名–SpaceId)

2. 新建表,檢視當前表空間id(假設space id為10)

#mysql -uroot –p****** -e”create table test.tt(a bool)”

#hexdump -C mysql/test/tt.ibd |head -n 3 |tail -n 1|awk ‘{print  strtonum(“0x”$6$7)}’

3. 先建立所有庫,準備所有表結構,寫指令碼,依據space id號自動建立新表

準備好資料庫表結構,可以從備份檔案裡取出來(我們備份方式是把結構和資料分開備份的),或者從其他有相同表結構的伺服器上備份再拷貝過來。

參考備份語句:

mysqldump -uroot –p****** -d ${db} –T /data/backup/${db}/

建立原有的資料庫:

mysql -uroot –p****** -e “create database ${db}”

恢復表id建立表指令碼:

#!/bin/bash
#因為前面假設為10,所以從11開始建立
oid=11

#開啟前面生成的ibd.txt檔案,按行讀取”庫名–表名–SpaceId”
cat /tmp/ibd.txt | while read db tb id ;do

#假如我們需要恢復catetory表,他的id為415,基於id是創表自增的原則,即415-11=404,
#我們還需要迴圈建立404個表後,才真正匯入catetory表結構。
for ((oid;oid<id;oid++)); do
mysql -uroot –p****** -e “create table test.t(a bool);drop table test.t;” && echo “${oid} ok”
done

#迴圈建立404次表後,id為415,與原來備份的.ibd檔案編號一致,匯入表結構
mysql -uroot –p****** ${db} < /data/backup/${db}/${tb%%.ibd}.sql && echo “${oid} ${db}/${tb%%.ibd}.sql ok”
let oid=oid+1
done

4. 檢查表空間id 和備份的是否一致

for ibd in `find test_restore/ -name “*.ibd”` ; do  echo -e “${ibd//\// }   \c” ;hexdump -C ${ibd} |head -n 3 |tail -n 1|awk ‘{print  strtonum(“0x”$6$7)}’ ;done | sort -n  -k 3 | column -t > /tmp/ibd2.txt

確認一致後,拷貝備份的.ibd檔案到新資料庫例項目錄下,修改my.cnf

innodb_force_recovery=6

innodb_purge_threads=0

啟動資料庫。後續步驟如同單表恢復,直接匯出恢復到原來例項中即可。

當然,這種方式是在資料庫出現極端情況下,不得不採取的一種方式,線上最重要的還是做好主從同步和定時備份,從而規避此類風險。

關於InnoDB引擎獨立表空間說明:

使用過MySQL的同學,剛開始接觸最多的莫過於MyISAM表引擎了,這種引擎的資料庫會分別建立三個檔案:表結構、表索引、表資料空間。我們可以將某個資料庫目錄直接遷移到其他資料庫也可以正常工作。然而當你使用InnoDB的時候,一切都變了。

InnoDB預設會將所有的資料庫InnoDB引擎的表資料儲存在一個共享空間中:ibdata1,這樣就感覺不爽,增刪資料庫的時候,ibdata1檔案不會自動收縮,單個數據庫的備份也將成為問題。通常只能將資料使用mysqldump匯出,然後再匯入解決這個問題。

但是可以通過修改MySQL配置檔案[mysqld]部分中innodb_file_per_table的引數來開啟獨立表空間模式,每個資料庫的每個表都會生成一個數據空間。

優點:

1.每個表都有自已獨立的表空間。

2.每個表的資料和索引都會存在自已的表空間中。

3.可以實現單表在不同的資料庫中移動。

4.空間可以回收(除drop table操作處,表空不能自已回收)

a) Drop table操作自動回收表空間,如果對於統計分析或是日值表,刪除大量資料後可以通過:alter table TableName engine=innodb;回縮不用的空間。

b) 對於使innodb-plugin的Innodb使用turncate table也會使空間收縮。

c) 對於使用獨立表空間的表,不管怎麼刪除,表空間的碎片不會太嚴重的影響效能,而且還有機會處理。

缺點:

單表增加過大,如超過100個G。

結論:

共享表空間在Insert操作上少有優勢。其它都沒獨立表空間表現好。當啟用獨立表空間時,請合理調整一下:innodb_open_files。

配置方式:

1.innodb_file_per_table設定.開啟方法:

在my.cnf中[mysqld]下設定

innodb_file_per_table=1

2.檢視是否開啟:

mysql> show variables like ‘%per_table%’;

3.關閉獨享表空間

innodb_file_per_table=0關閉獨立的表空間

mysql> show variables like ‘%per_table%’;