MySQL Innodb數據庫誤刪ibdata1后MySQL數據庫的恢復案例


 

上周,以前公司的同事朋友找我幫忙,看看能否幫忙恢復一個MySQL 數據庫,具體情況為:數據庫版本為MySQL 5.6(具體版本不清楚),也不清楚具體的數據庫引擎; 沒有數據庫備份,只剩下數據庫下面的一些文件(frm、idb),具體原因是因為出現問題的時候,重裝了MySQL,最要命的是ibdata1等文件也沒有了,當然這中間細節過程如何,不清楚也不用去糾結了。大概就是這么一個情況。

 

 

因為數據庫不大,將對應的文件拷貝到自己一台測試服務器的MySQL數據文件目錄下后(下面實驗測試,對數據庫名等敏感信息做了一下混淆),如下所示,數據庫名為test,show tables可以看到相關的表。

 

 

clip_image002

 

 

其中有幾張表的存儲引擎為MyISAM,那么這些表的數據是完全可以恢復的,但是大部分表的存儲引擎為InnoDB,訪問表或查看表都會提示ERROR 1146 (42S02): Table 'xxxx' doesn't exist 不存在。

 

mysql> desc think_cache;
ERROR 1146 (42S02): Table 'test.think_cache' doesn't exist
mysql> show create table think_cache;
ERROR 1146 (42S02): Table 'test.think_cache' doesn't exist
mysql> 

 

由於共享表空間的ibdata1數據文件不存在了,加之有沒有備份,所以我武斷的判斷這個數據庫真的無法恢復了,但是過后一天,這個朋友跟我說找了一家數據恢復公司將這個數據庫恢復了。 聽到這個消息頗有點學藝不精的尷尬(其實談不上尷尬吧,本來還在學習MySQL的路上,有些知識點不清楚也很正常。經驗是需要慢慢積累的),不過更多的是好奇別人是如何恢復數據的,既然別人能夠恢復,那么自己下一次遇到這種情況也要能搞定。下面就來復盤一下別人是如何恢復數據的(其實只要稍稍做點功課,發現這個其實挺簡單的)

 

首先,我們來了解一下MySQL 表空間數據文件idbdat1文件相關概念和知識點:

 

    InnoDB采用按表空間(tablespace)的方式進行存儲數據, 默認配置情況下會有一個初始大小為10MB, 名字為ibdata1的文件, 該文件就是默認的表空間文件(tablespce file),用戶可以通過參數innodb_data_file_path對其進行設置,可以有多個數據文件,如果沒有設置innodb_file_per_table的話, 那些Innodb存儲類型的表的數據都放在這個共享表空間中,而系統變量innodb_file_per_table=1的話,那么InnoDB存儲引擎類型的表就會產生一個獨立表空間,獨立表空間的命名規則為:表名.idb. 這些單獨的表空間文件僅存儲該表的數據、索引和插入緩沖BITMAP等信息,其它信息還是存放在共享表空間中。

 

    其實當時主要是對這個概念有點模糊了,以為這個系統變量innodb_file_per_table默認是關閉的,數據都會存儲在共享表空間中,那么這些文件刪除了,數據就無法恢復。所以武斷的下結論,其實從MySQL 5.6.6開始, 系統變量innodb_file_per_table默認是啟用的。只要再多了解一點或者說更深入了解一點的話,情況就會立馬就會反轉。也就是說如果開啟了獨立表空間,可從ibd文件中恢復數據。即使共享表空間的數據文件idbdata1丟失也不要緊,反之,如果未開啟獨立表空間時,idbdat1被刪除了,數據也會被刪除,只能從備份中恢復,真的沒有其他辦法。

 

 

那么我們接下來看看,如何從idb文件中恢復數據吧,我們需要用到mysqlfrm工具, 需要安裝MySQL Utilities,下面是安裝MySQL Utilities 1.5.5

 

# tar -xvf mysql-utilities-1.5.5.tar.gz

# cd mysql-utilities-1.5.5

# python ./setup.py build

# python ./setup.py install

 

 

提取frm文件的表結構信息

 

mysqlfrm 是一個恢復性質的工具,用來讀取.frm文件並從該文件中找到表定義數據生成CREATE語句。此處不對mysqlfrm工具做過多介紹,我們使用msqlfrm來生成該數據庫的表的CREATE語句

 

[root@DB-Server ~]# service mysql stop
Shutting down MySQL.... SUCCESS! 
[root@DB-Server ~]# /usr/local/bin/mysqlfrm --basedir=/usr --port=3306 --user=root /data/mysql/test/ > test_frm.sql
[root@DB-Server ~]# 

 

檢查導出的SQL語句發現都是ERROR: The server version for this file is too low. It requires a server version 5.6.29 or higher but your server is version 5.6.20. Try using a newer server or use diagnostic mode這類錯誤

 

[root@DB-Server ~]# more test_frm.sql 
# Spawning server with --user=root.
# Starting the spawned server on port 3306 ... done.
# Reading .frm files
#
# Reading the think_cache.frm file.
ERROR: The server version for this file is too low. It requires a server version 5.6.29 or higher but your server is version 5.6.20. Try using a newer server or use diagnostic mode.
#
# Reading the think_session.frm file.
ERROR: The server version for this file is too low. It requires a server version 5.6.29 or higher but your server is version 5.6.20. Try using a newer server or use diagnostic mode.
#
# Reading the wx_activity_config.frm file.
ERROR: The server version for this file is too low. It requires a server version 5.6.29 or higher but your server is version 5.6.20. Try using a newer server or use diagnostic mode.
#
........................................................................................

 

從中可以看到這個數據庫之前的版本為MySQL5.6.29而我這里的MySQL版本比這個低MySQL 5.6.20。所以必須找一個跟這個版本相同或高的MySQL數據庫操作才行。於是在另外一台測試服務器安裝了MySQL

 

[root@gettestlnx02 ~]# service mysqld stop
 
Stopping mysqld:  [  OK  ]
 
[root@gettestlnx02 tmp]# mv test  /data/mysqldata/mysql/test
 
[root@gettestlnx02 tmp]# cd /data/mysqldata/mysql/

 

/usr/bin/mysqlfrm --basedir=/usr --port=3306 --user=root /data/mysqldata/mysql/test/ > test_frm.sql

 

如何要查看輸出信息,可以使用參數-vvv

 

/usr/bin/mysqlfrm --basedir=/usr --port=3306 --user=root -vvv /data/mysqldata/mysql/test/ > test_frm.sql

 

clip_image003

 

生成的SQL腳本沒有以分號結尾,本來想用sed命令給那些CREATE TABLE腳本加上分號結尾,但是發現其中大量CREATE TABLE的腳本結尾沒有規律,都是以COMMNET='xxxxx'結尾,只能手工添加分號(如下所示)

 

 

clip_image004

 

 

導入frm文件的表結構信息

 

mysql> use test;
Database changed
mysql> source test_frm.sql
Query OK, 0 rows affected (0.02 sec)
 
Query OK, 0 rows affected (0.01 sec)
 
Query OK, 0 rows affected (0.01 sec)
 
Query OK, 0 rows affected (0.00 sec)
.................................

 

然后我們檢查這個數據庫的各類文件frm、ibd、MYI、MYD文件數量,后續做對比驗證用途。

 

[root@gettestlnx02 test]# ls -lrt *.frm | wc -l

84

[root@gettestlnx02 test]# ls -lrt *.ibd | wc -l  

84

[root@gettestlnx02 test]# ls -lrt *.MYI | wc -l     

22

[root@gettestlnx02 test]# ls -lrt *.MYD | wc -l 

22

[root@gettestlnx02 test]#

 

 

刪除新建表的獨立表空間文件

 

使用下面腳本生成刪除新建表的獨立空間的腳本:

 

select concat(concat('alter table ',table_name), ' discard tablespace;')                                                  
from information_schema.tables                               
where table_schema='test' and engine ='InnoDB';

 

使用腳本就可以生成下面SQL,執行該命令后,對應數據庫下面的ibd文件全部被刪除。

 

alter table think_cache discard tablespace;      

alter table think_session discard tablespace;    

alter table wx_activity_config discard tablespace;

........................................

 

 

 

復制待恢復的表空間文件

 

將待恢復的ibd文件拷貝到對應數據庫目錄下面,並設置好權限屬性

 

# cd /tmp/database
# ls -lrt *.ibd | wc -l
84
# cp *.ibd /var/lib/mysql/test
 
# chown  mysql:mysql *.ibd
# chmod 660 *.ibd

 

 

導入表空間

mysql> alter table think_cache import tablespace;
Query OK, 0 rows affected, 1 warning (0.21 sec)
 
mysql> alter table think_session import tablespace;
Query OK, 0 rows affected, 1 warning (0.18 sec)
 
mysql> select count(*) from think_cache;
+----------+
| count(*) |
+----------+
|    10919 |
+----------+
1 row in set (0.01 sec)
 
mysql> select * from think_cache limit 5;
+---------------------------------------+------------+----------------------------------------+---------+
| cachekey                              | expire     | data                                   | datacrc |
+---------------------------------------+------------+----------------------------------------+---------+
| 00OLH9JvIwX42R3mPygXYN3gWZp2rH_rebate | 1533050257 | s:30:"00OLH9JvIwX42R3mPygXYN3gWZp2rH"; |         |
| 00SCWX7cIgqnnzHRArAXoascr1gnlA_rebate | 1516937278 | s:30:"00SCWX7cIgqnnzHRArAXoascr1gnlA"; |         |
| 00uVkAbOMPGQc2z02PPxVMblGY7oj7_rebate | 1528708564 | s:30:"00uVkAbOMPGQc2z02PPxVMblGY7oj7"; |         |
| 01dB7czgCph7hgm1qGM7qA7haChXop_rebate | 1525740805 | s:30:"01dB7czgCph7hgm1qGM7qA7haChXop"; |         |
| 023oMqQAAwg4WWxWgJSLNgQhYlgtVi_rebate | 1531560804 | s:30:"023oMqQAAwg4WWxWgJSLNgQhYlgtVi"; |         |
+---------------------------------------+------------+----------------------------------------+---------+
5 rows in set (0.00 sec)
 
mysql> select count(*) from think_session;
+----------+
| count(*) |
+----------+
|     1347 |
+----------+
1 row in set (0.00 sec)
 
mysql> select * from think_session limit 5;
+----------------------------+----------------+--------------+
| session_id                 | session_expire | session_data |
+----------------------------+----------------+--------------+
| 00onr4u3jabvi6vrts3bfeaqt4 |     1533358643 |              |
| 00rs65ljphuhhughujfnk2bci6 |     1533350110 |              |
| 01ld93n8ac31o4uorqrebtjir5 |     1533418040 |              |
| 01u5tv79pp8jjssh1r3s7oj6d4 |     1533351181 |              |
| 0261rcndf0jmq9dccou5l23mn4 |     1533346621 |              |
+----------------------------+----------------+--------------+
5 rows in set (0.00 sec)

 

 

 

導出數據庫

 

導入數據庫

 

 

如果順利的話,一切就正常了,數據正常恢復,是否也不是什么難事,難就難在你不知道而已,如果你認證學習了一下這方面的知識點,整個事情其實並不復雜。有些細節操作問題可以參考官方文檔:

 

https://dev.mysql.com/doc/refman/8.0/en/innodb-troubleshooting-datadict.html

https://dev.mysql.com/doc/refman/5.6/en/innodb-troubleshooting-datadict.html

 

 

 

那么在這個恢復過程中是否會遇到一些麻煩或問題呢,答案是肯定的,下面簡單介紹一些在恢復過程中可能遇到的問題

 

1: 在實驗測試過程,我一度使用版本為MySQL 5.7.21的數據庫,在導入表空間是遇到下面錯誤:

 

mysql> alter table wx_sign_record import tablespace; 
 
ERROR 1808 (HY000): Schema mismatch (Table has ROW_TYPE_DYNAMIC row format, .ibd file has ROW_TYPE_COMPACT row format.)
 
mysql> alter table wx_sign_record  row_format=DYNAMIC;
 
ERROR 2013 (HY000): Lost connection to MySQL server during query
 
mysql> alter table wx_sign_record  row_format=DYNAMIC;
 
ERROR 2006 (HY000): MySQL server has gone away
 
No connection. Trying to reconnect...
 
Connection id:    2
 
Current database: test

 

后面在網上找到相關資料可能是因為數據庫版本緣故我使用MySQL 5.6.41這個版本就沒有遇到這個問題。所以如果遇到這種數據恢復案例,最好使用相同的數據庫版本。

 

 

2:表空間 id 不一致問題

  

    個人沒有遇到這個問題,這里不做介紹。可以參考MySQL 數據恢復案例

 

 

3:腳本自動化問題 

 

   對於研究問題,可以手工操作,但是最好通過腳本自動化操作,MySQL 數據恢復案例里有自動化腳本放在github上,有興趣可以參考!

 

 

 

原理介紹:

 

關於原理介紹,可以參考英文原文The basics of InnoDB space file layout 或者MySQL 數據恢復案例下面這部分內容完全摘抄自MySQL 數據恢復案例

 

恢復方案中,我們使用到了 DISCARD TABLESPACE、IMPORT TABLESPACE 和修改表空間 id。我們先說下 InnoDB 數據頁的組成。InnoDB 數據頁由 7 個部分組成,分別是 File Header、Page Header、Infimum 和 Supermum Records、User Records、Free Space 和 Page Directory。

接下來看看 ibdata 文件的組織結構,如下圖:

clip_image006
From
 blog.jcole.us, by Jeremy Cole.

然后看看 ibd 文件的組織結構,如下圖:

clip_image007
From
 blog.jcole.us, by Jeremy Cole.

我們要修改的表空間 id位於 FSP_HEADER。不同的 ibd 文件表空間 id 是不同的。ibdata 文件中有一個數據字典 data dictionary,記錄的是實例中每個表在 ibdata 中的一個邏輯位置,而在 ibd 文件中也存儲着同樣的一個 tablespace id,兩者必須一致,InnoDB 引擎才能正常加載到數據。所以,我們需要修改舊的表空間 id 為新的。

實際上,我們對於 ibdata 文件中的 undo、change buffer、double write buffer 數據可以不用關心。我們只需要利用一個全新的實例,以及一個干凈的 ibdata 文件,通過卸載和加載表空間把 ibd 文件與 ibdata 文件關聯。筆者使用了這么多腳本,目的就是如此。

 

 

參考資料:

 

https://serverfault.com/questions/698038/mysql-innodb-recovery-from-datafiles

https://dbarobin.com/2016/04/23/ibd-recovery/

 


免責聲明!

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



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