導讀 | 磁碟壞道、斷電等意外不是常態,但遇上了就足夠你「驚心動魄」!如果是資料庫損壞造成的資料遺失,Binlog也不可用了,怎麼辦?為了在短時間內無損恢復資料以確保業務穩定性,除了利用binlog,我們還修練了一招新的恢復技能! |
還記得我們之前寫過的《只需一招,讓失控的研發愛上你》嗎?前文提到我們日常使用的比較多的兩種資料庫復原方法是:
#以上兩種方法都可以實現即時性的回檔,但是你會認為有了這兩種技能就夠了嗎?
不….!
在線上這種錯綜複雜的架構中,其實還有很多未知的原因,我們是沒法預知的。例如以下這種情況:
因辛勤工作而折壽的磁碟產生成長壞道,導致資料庫損壞。而又剛好損壞了ibdata檔和binlog檔。那如果還想著以定時備份 binlog恢復的方案就不可能了,難道只能用定點備份回檔嗎?經過深思熟慮後,身為維運人員,我們是絕對不會在萬不得已的情況下實行有損回檔,因為這對業務產生太大的影響了,但是除此之外又能怎麼辦呢?下面我們將要放一門大招! ! !
先檢查資料庫環境,是否開啟了獨立表空間,如果已經開啟的話,那恭喜你,有很大的機會可以恢復全部資料。我們可以依賴每個資料庫目錄下的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
#mysql -uroot –p**** -e “create database test_restore”
#mysql -uroot –p**** test_restore 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的解釋:
#参考官方文档解释,每个表空间分配了4个字节存储了表空间id信息,最后偏移量地址为38。还有一组预留的表空间id,同样是4个字节,最后偏移量地址为42。
3. 验证并还原mysql数据关闭mysql。修改my.conf。
innodb_force_recovery=6 innodb_purge_threads=0
启动数据库。如果不修改。数据库会认为G_RESTORE已被损坏。
Select 一下,即可查看到还原结果,但此时插入数据会报错,应尽快将数据dump出来 ,导回原来的实例中。
导出数据,再导入数据,恢复完毕!
#mysqldump -uroot –p****** test_restore > test_restore.sql #mysql -uroot –p****** test_restore <p>说明:变更了新的space id后的.ibd表文件,启动数据库后只能认出数据,但不能写入,这是因为原ibdata文件不仅保存了space id索引,还同时保存了一些其它的元数据。为了使元数据补全,所以采取导出、再导入的操作。</p> <p>以上举例为单个库表的恢复过程,看到这里大家一定会产生另一个疑问吧?线上的场景不可能是只有一个表的,数据库表很多的情况下,这样一个个表的修改,速度无疑是太慢了。那么存在大量表的情况下如何恢复呢?思路是,取得备份的ibd文件的id值,按id值顺序来建表,中间跨度随便建表语句来凑够数(每个表空间索引id由创建新表的数量依次递增)。实现方式如下:</p> <p><span style="color: #339966;"><strong>1. 获取备份数据库ibd文件的space id号,并排序。</strong></span></p> <pre class="brush:php;toolbar:false">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 do mysql table test.t bool echo ok done let oid="oid+1"> <p><span style="color: #339966;"><strong>4. 检查表空间id 和备份的是否一致</strong></span></p> <pre class="brush:php;toolbar:false">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%’;
以上是MySQL借助ibd檔案恢復資料技巧?的詳細內容。更多資訊請關注PHP中文網其他相關文章!