最近內部的 mysql 資料庫發生了一件奇怪的事,其中有一個表 users625 突然出現問題, 所有對它的操作都報錯誤 。 它還顯示在列表裡,在 mysql 數據目錄中也可以找到對應的表文件,也沒有 進行過刪除操作,突然出現這樣的錯誤非常奇怪。 <! more 內部運行環境: | 名稱 | 值 | ...
最近內部的 mysql 資料庫發生了一件奇怪的事,其中有一個表 users625 突然出現問題,
所有對它的操作都報錯誤 數據表不存在
。
mysql> select count(*) from users625;
ERROR 1146 (42S02): Table 'km8.users625' doesn't exist
show tables
它還顯示在列表裡,在 mysql 數據目錄中也可以找到對應的表文件,也沒有
進行過刪除操作,突然出現這樣的錯誤非常奇怪。
內部運行環境:
名稱 | 值 |
---|---|
OS | Debian Squeeze x64 |
mysql 版本 | 5.1 |
mysql 引擎 | innodb |
發生了什麼
突然出現這種情況,第一反應必定是想辦法將表中的用戶數據找回,但是目前發生問題的情況與原因都不明晰,
不能輕舉妄動。
查看 mysql 日誌,在操作出錯的時候,日誌這樣顯示:
mysqld: 180926 11:10:53 InnoDB: cannot calculate statistics for table km8/users625
mysqld: InnoDB: because the .ibd file is missing. For help, please refer to
mysqld: InnoDB: http://dev.mysql.com/doc/refman/5.1/en/innodb-troubleshooting.html
mysqld: 180926 11:10:53 [ERROR] MySQL is trying to open a table handle but the .ibd file for
mysqld: table km8/users625 does not exist.
mysqld: Have you deleted the .ibd file from the database directory under
mysqld: the MySQL datadir, or have you used DISCARD TABLESPACE?
mysqld: See http://dev.mysql.com/doc/refman/5.1/en/innodb-troubleshooting.html
mysqld: how you can resolve the problem.
其中提到3個可追溯的點:
- ibd file
- DISCARD TABLESPACE
- http://dev.mysql.com/doc/refman/5.1/en/innodb-troubleshooting.html
瞭解這3點提到的內容,應該對判斷情況有很好的幫助。
ibd file
日誌中提問,是否丟失了 ibd 文件?先到 mysql 數據目錄下查找,
.
├── ibdata1
├── .......
├── .......
└── km8
├── ............
├── ............
├── users625.frm
├── users625.ibd
├── ............
└── ............
users625 的 ibd 文件是存在的,與之一起的還有文件 users625.frm 。
根據官方文檔對 frm 文件的描述,frm 文件是用來保存 table 表結構(即 table 的定義)的,無論使用什麼存儲引擎。
與之相對的,ibd 文件是用來存儲表數據(即行數據)的,通常情況下,所有數據都會存儲在系統的 ibd 文件,
但是當開啟選項 innodb_file_per_table 的時候,每個表的數據會使用單獨的 ibd 文件來存儲。
當前的 mysql 就開啟了這個選項,
[mysqld]
innodb_file_per_table=1
目前 frm 與 ibd 文件都存在,從中恢複數據便存在一些希望。
DISCARD TABLESPACE
日誌中提到的 DISCARD TABLESPACE
其實是在猜測導致 ibd 文件丟失的原因,因為它會刪除相應 table 的 ibd 文件(所謂 tablespace)。
> ALTER TABLE km8.users625 DISCARD TABLESPACE;
底層的 users625.ibd 文件就會被刪除,丟失所有表數據。
根據目前情況來看, ibd 文件還存在,所以它不是導致錯誤的原因。
trouble shooting doc
日誌中提到的參考鏈接,其中列舉了多種情況,和當前問題相關的是一個子鏈接,
按照它提供的方法,嘗試進行數據恢復。
數據恢復
官方文檔提到的恢複數據的方法,思路很清晰:
- 啟用相同版本的 mysql 實例(啟用選項 innodb_file_per_table)
- 建立同樣結構的數據表
- 替換 ibd 文件(保持文件許可權一致)
- 導入 ibd 文件中的數據
- 使用 mysqldump,導出數據
- 將導出的數據導入原資料庫
我按照這種方式嘗試恢複數據,並不是那麼順利:
如何獲得 table 表結構?
在第2步,需要建立同樣結構的數據表,目前只有 frm 和 ibd 文件,怎麼樣得到 create table
命令?
根據底層數據存儲的理解,table 表結構存儲在 frm 文件中,而目前已經有相應的方法從中提取出 create table
命令,
這樣就可以用於在新的 mysql 實例中建立 table 。
tablespace id 不對應?
在第4步,嘗試導入數據的時候,
> ALTER TABLE km8.users625 IMPORT TABLESPACE;
ERROR 1030 (HY000): Got error -1 from storage engine
總是出現失敗,同時在 mysql 新實例的日誌中發現這樣的錯誤:
mysqld: InnoDB: Error: tablespace id in file './km8/users625.ibd' is 18446744073709551615, but in the InnoDB
mysqld: InnoDB: data dictinary it is 1.
原來在內部,ibd 文件本身有一個 id,必須和 mysql innodb 內部的 table 元數據相對應,才可以進行導入。
根據錯誤信息搜索到一篇文章,其中提到兩種辦法:
- 重覆建表,因為 mysql 內部的 tablespace id 是累計遞增的,預先建立 (18446744073709551615 - 1)張表,再建立
users625 表,就可以對應 id,併進行導入。 - 修改 ibd 文件,因為 tablespace id 存儲於 ibd 文件,找到它並將其修改為 1,使之與內部的 id 對應,就可以進行導入。
考慮第 1 種方法,要預先建立上億張空表?!這根本不可能。
於是嘗試第 2 種方法,研究 ibd 的文件格式,修改對應 id。
用二進位編輯器打開 users625.ibd 文件,
18:26:08 UTC - mysqld got signal 6. This could be because you hit a bug. It is also possible that this
binary or one of the libraries it was linked against is corrupt, improperly built or misconfigured.
This error can also be cuased by malfunctioning hardware.
不敢相信自己的眼睛,居然有錯誤 log 在二進位文件里?!ibd 的文件格式可沒有這麼說明過。
隨便找一個鄰居表正常的 ibd 文件作對比,
看來是出現了 bug ,崩潰的環境直接將數據文件給毀了,這也解釋了為什麼 tablespace id 會那麼大,因為 log
覆蓋了原本的 id 欄位,使 mysql 解讀出了一個好笑的數字。
暫時放棄
這種情況下,還沒有辦法將數據恢復回來,只能暫時將表刪除,新建空表,保證上層應用程式可以運行。
將 ibd 文件備份下來,看後續還沒有其它的辦法將其恢復。
檢測所有 table 狀態
當前只發現一個出現問題的 table ,可能同時也有其它的 table 出現問題。對此需要做一個全面的檢測,
檢測有沒有其它的表受到牽連。
$ mysqlcheck --all-databases
寫在最後
資料庫的備份是非常重要的!直接導入備份數據,是解決問題最保險最便捷的辦法。
如果沒有備份,遇到 bug 丟失數據,只能怪時運不濟。
同時資料庫也最好選擇穩定的版本,降低出現 bug 的概率。