MySQL數(shù)據(jù)庫InnoDB數(shù)據(jù)恢復(fù)工具的使用小結(jié)詳解
更新時間:2013年06月03日 11:36:16 作者:
本篇文章是對MySQL數(shù)據(jù)庫InnoDB數(shù)據(jù)恢復(fù)工具的使用進(jìn)行了詳細(xì)的總結(jié)與分析,需要的朋友參考下
本文從實際使用經(jīng)驗出發(fā),介紹一款開源的MySQL數(shù)據(jù)庫InnoDB數(shù)據(jù)恢復(fù)工具:innodb-tools,它通過從原始數(shù)據(jù)文件中提取表的行記錄,實現(xiàn)從丟失的或者被毀壞的MySQL表中恢復(fù)數(shù)據(jù)。例如,當(dāng)你不小心執(zhí)行DROP TABLE、TRUNCATE TABLE或者DROP DATABASE之后,可以通過以下方式恢復(fù)數(shù)據(jù)。
以下內(nèi)容大部分參考自:Percona Data Recovery Tool for InnoDB,文檔是英文的,而且寫的比較晦澀,這里是個人的實戰(zhàn)經(jīng)驗總結(jié),供大家參考學(xué)習(xí)。
在介紹innodb-tools工具進(jìn)行數(shù)據(jù)恢復(fù)之前,首先明確以下幾點:
1、這個工具只能對InnoDB/XtraDB表有效,而無法恢復(fù)MyISAM表(注: Percona號稱有一套用于恢復(fù)MyISAM表的工具,但是本人未做嘗試)。
2、這個工具是以保存的MySQL數(shù)據(jù)文件進(jìn)行恢復(fù)的,而不用MySQL Server運行。
3、不能保證數(shù)據(jù)總一定可被恢復(fù)。例如,被重寫的數(shù)據(jù)不能被恢復(fù),這種情況下可能需要針對系統(tǒng)或物理的方式來恢復(fù),不屬于本工具的范疇。
4、恢復(fù)的最好時機是當(dāng)你發(fā)現(xiàn)數(shù)據(jù)丟失時,盡快備份MySQL數(shù)據(jù)文件。
5、使用這個工具需要手動做一些工作,并不是全自動完成的。
6、恢復(fù)過程依賴于你對丟失數(shù)據(jù)的了解程度,在恢復(fù)過程中可能需要在不同版本的數(shù)據(jù)之間做出選擇。那么如果你越了解自己的數(shù)據(jù),恢復(fù)的可能性就越大。
接下來,下面通過一個例子來介紹如何通過這個工具進(jìn)行恢復(fù)。
1. 前提條件
首先,需要理解的是innodb-tools工具不是通過連接到在線的database進(jìn)行數(shù)據(jù)恢復(fù),而是通過離線拷貝數(shù)據(jù)的方式進(jìn)行的。注意:不要在MySQL運行的時候,直接拷貝InnoDB文件,這樣是不安全的,會影響數(shù)據(jù)恢復(fù)過程。
為了完成數(shù)據(jù)恢復(fù),必須知道將要被恢復(fù)的表結(jié)構(gòu)(列名、數(shù)據(jù)類型)。最簡單的方式就是SHOW CREATE TABLE,當(dāng)然后續(xù)會介紹幾種可替代的方式。因此,如果有一個MySQL server作為備份,即使數(shù)據(jù)是很早的甚至表中沒有記錄,可以有助于使用innodb-tools工具進(jìn)行恢復(fù)。不過這個不是必須的。
2. 簡單例子
mysql> TRUNCATE TABLE customer;
3. 構(gòu)建工具
為了構(gòu)建innodb-tools工具,需要依賴于C編譯器、make工具等。
1、下載解壓innodb-tools工具源碼:
wget https://launchpad.net/percona-data-recovery-tool-for-innodb/trunk/release-0.5/+download/percona-data-recovery-tool-for-innodb-0.5.tar.gztar -zxvf percona-data-recovery-tool-for-innodb-0.5.tar.gz
2、進(jìn)入解壓后根目錄下的mysql-source目錄,運行配置命令(注:不運行make命令):
cd percona-data-recovery-tool-for-innodb-0.5/mysql-source
./configure
3、完成配置步驟后,回到解壓后的根目錄,運行make命令,編譯生成page_parser和constraints_parser工具:
cd ..
make
page_parser工具將根據(jù)InnoDB的底層實現(xiàn)原理,解析表的頁和行結(jié)構(gòu)。constraints_parser工具暫時不使用,后續(xù)還需要在定義表結(jié)構(gòu)之后,重新編譯生成它。
如果編譯過程中出現(xiàn)問題,點擊這里。本文使用過程中沒有出現(xiàn)問題,故不再一一列舉。
4. 提取需要的頁
InnoDB頁的默認(rèn)大小是16K,每個頁屬于一個特定表中的一個特定的index。page_parser工具通過讀取數(shù)據(jù)文件,根據(jù)頁頭中的index ID,拷貝每個頁到一個單獨的文件中。
如果你的MySQL server被配置為innodb_file_per_table=1,那么系統(tǒng)已經(jīng)幫你實現(xiàn)上述過程。所有需要的頁都在.ibd文件,而且通常你不需要再切分它。然而,如果.ibd文件中可能包含多個index,那么將頁單獨切分開還是有必要的。如果MySQL server沒有配置innodb_file_per_table,那么數(shù)據(jù)會被保存在一個全局的表命名空間(通常是一個名為ibdata1的文件,本文屬于這種情況),這時候就需要按頁對文件進(jìn)行切分。
4.1 切分頁
運行page_parser工具進(jìn)行切分:
•如果MySQL是5.0之前的版本,InnoDB采取的是REDUNDANT格式,運行以下命令:
./page_parser -4 -f /path/to/ibdata1
•如果MySQL是5.0版本,InnoDB采取的是COMPACT格式,運行以下命令:
./page_parser -5 -f /path/to/ibdata1
運行后,page_parser工具會創(chuàng)建一個pages-<TIMESTAMP>的目錄,其中TIMESTAMP是UNIX系統(tǒng)時間戳。在這個目錄下,為每個index ID,以頁的index ID創(chuàng)建一個子目錄。例如:
pages-1330842944/FIL_PAGE_INDEX/0-1/1-00000008.page
pages-1330842944/FIL_PAGE_INDEX/0-1/6-00000008.page
4.2 選擇需要的Index ID
一般來說,我們需要根據(jù)表的主鍵(PRIMARY index)進(jìn)行恢復(fù),主鍵中包含了所有的行。以下是一些可以實現(xiàn)的步驟:
如果數(shù)據(jù)庫仍處于運行狀態(tài),并且表沒有被drop掉,那么可以啟動InnoDB Tablespace Monitor,輸出所有表和indexes,index IDs到MySQL server的錯誤日志文件。創(chuàng)建innodb_table_monitor表用于收集innodb存儲引擎表及其索引的存儲方式:
mysql> CREATE TABLE innodb_table_monitor (id int) ENGINE=InnoDB;
如果innodb_table_monitor已經(jīng)存在,drop表然后重新create表。等MySQL錯誤日志輸出后,可以drop掉這張表以停止打印輸出更多的監(jiān)控。一個輸出的例子如下:
TABLE: name sakila/customer, id 0 142, columns 13, indexes 4, appr.rows 0
COLUMNS: customer_id: DATA_INT len 2 prec 0; store_id: DATA_INT len 1 prec 0; first_name: type 12 len 135 prec 0; last_name: type 12 len 135 prec 0; email:
type 12 len 150 prec 0; address_id: DATA_INT len 2 prec 0; active: DATA_INT len 1 prec 0; create_date: DATA_INT len 8 prec 0; last_update: DATA_INT len 4 pr
ec 0; DB_ROW_ID: DATA_SYS prtype 256 len 6 prec 0; DB_TRX_ID: DATA_SYS prtype 257 len 6 prec 0; DB_ROLL_PTR: DATA_SYS prtype 258 len 7 prec 0;
INDEX: name PRIMARY, id 0 286, fields 1/11, type 3
root page 50, appr.key vals 0, leaf pages 1, size pages 1
FIELDS: customer_id DB_TRX_ID DB_ROLL_PTR store_id first_name last_name email address_id active create_date last_update
INDEX: name idx_fk_store_id, id 0 287, fields 1/2, type 0
root page 56, appr.key vals 0, leaf pages 1, size pages 1
FIELDS: store_id customer_id
INDEX: name idx_fk_address_id, id 0 288, fields 1/2, type 0
root page 63, appr.key vals 0, leaf pages 1, size pages 1
FIELDS: address_id customer_id
INDEX: name idx_last_name, id 0 289, fields 1/2, type 0
root page 1493, appr.key vals 0, leaf pages 1, size pages 1
FIELDS: last_name customer_id
這里,我們恢復(fù)的是sakila庫下的customer表,從上面可以獲取其主鍵信息:
INDEX: name PRIMARY, id 0 286, fields 1/11, type 3
Index ID是0 256,因此我們需要恢復(fù)的InnoDB頁位于0-256子目錄下。
備注:參考文檔原文中之描述了以上這種獲取表的index ID的方法,本文在實際操作中,采取了更簡單的一種方式,即直接恢復(fù)page_parser生成的所有InnoDB頁。實踐證明這種方法也是可行的:)
5. 生成表定義
步驟4中,我們已經(jīng)找到了需要的數(shù)據(jù),接下來需要找到表結(jié)構(gòu),創(chuàng)建表定義,將其編譯到constraints_parser中,然后使用這個工具從InnoDB頁中提取表中的行。
表定義包含了表中的列、列順序、數(shù)據(jù)類型。如果MySQL server仍處于運行且表未被drop掉,那么簡單實用SHOW CREATE TABLE就可以收集到這些信息。接下來將使用這些表結(jié)構(gòu)信息來創(chuàng)建一個C結(jié)構(gòu)體標(biāo)識的表定義,然后編譯到constraints_parser工具。C結(jié)構(gòu)體的定義存放在include/table_defs.h中。
最簡單的方式是create_defs.pl Perl 腳本,連接到MySQL server,讀取SHOW CREATE TABLE的結(jié)果,輸出生成的表定義到標(biāo)準(zhǔn)輸出。下面是個例子,其中直接將結(jié)果重定向到了include/table_defs.h中:
If possible, the easiest way to create the table definition is with the create_defs.pl Perl script. It connects to the MySQL server and reads SHOW CREATE TABLE output, and prints the generated definition to its standard output. Here is an example:
$ ./create_defs.pl --host=localhost --user=root --password=123456 --db=sakila --table=customer > include/table_defs.h
下面是例子中的表結(jié)構(gòu):
CREATE TABLE `customer` (
`customer_id` smallint(5) UNSIGNED NOT NULL AUTO_INCREMENT,
`store_id` tinyint(3) UNSIGNED NOT NULL,
`first_name` varchar(45) NOT NULL,
`last_name` varchar(45) NOT NULL,
`email` varchar(50) DEFAULT NULL,
`address_id` smallint(5) UNSIGNED NOT NULL,
`active` tinyint(1) NOT NULL DEFAULT '1',
`create_date` datetime NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`customer_id`),
KEY `idx_fk_store_id` (`store_id`),
KEY `idx_fk_address_id` (`address_id`),
KEY `idx_last_name` (`last_name`),
CONSTRAINT `fk_customer_address` FOREIGN KEY (`address_id`) REFERENCES `address` (`address_id`) ON UPDATE CASCADE,
CONSTRAINT `fk_customer_store` FOREIGN KEY (`store_id`) REFERENCES `store` (`store_id`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
下面是生成的表定義:
#ifndef table_defs_h
#define table_defs_h
// Table definitions
table_def_t table_definitions[] = {
{
name: "customer",
{
{ /* smallint(5) unsigned */
name: "customer_id",
type: FT_UINT,
fixed_length: 2,
has_limits: TRUE,
limits: {
can_be_null: FALSE,
uint_min_val: 0,
uint_max_val: 65535
},
can_be_null: FALSE
},
{ /* Innodb's internally used field */
name: "DB_TRX_ID",
type: FT_INTERNAL,
fixed_length: 6,
can_be_null: FALSE
},
{ /* Innodb's internally used field */
name: "DB_ROLL_PTR",
type: FT_INTERNAL,
fixed_length: 7,
can_be_null: FALSE
},
{ /* tinyint(3) unsigned */
name: "store_id",
type: FT_UINT,
fixed_length: 1,
has_limits: TRUE,
limits: {
can_be_null: FALSE,
uint_min_val: 0,
uint_max_val: 255
},
can_be_null: FALSE
},
{ /* varchar(45) */
name: "first_name",
type: FT_CHAR,
min_length: 0,
max_length: 45,
has_limits: TRUE,
limits: {
can_be_null: FALSE,
char_min_len: 0,
char_max_len: 45,
char_ascii_only: TRUE
},
can_be_null: FALSE
},
{ /* varchar(45) */
name: "last_name",
type: FT_CHAR,
min_length: 0,
max_length: 45,
has_limits: TRUE,
limits: {
can_be_null: FALSE,
char_min_len: 0,
char_max_len: 45,
char_ascii_only: TRUE
},
can_be_null: FALSE
},
{ /* varchar(50) */
name: "email",
type: FT_CHAR,
min_length: 0,
max_length: 50,
has_limits: TRUE,
limits: {
can_be_null: TRUE,
char_min_len: 0,
char_max_len: 50,
char_ascii_only: TRUE
},
can_be_null: TRUE
},
{ /* smallint(5) unsigned */
name: "address_id",
type: FT_UINT,
fixed_length: 2,
has_limits: TRUE,
limits: {
can_be_null: FALSE,
uint_min_val: 0,
uint_max_val: 65535
},
can_be_null: FALSE
},
{ /* tinyint(1) */
name: "active",
type: FT_INT,
fixed_length: 1,
can_be_null: FALSE
},
{ /* datetime */
name: "create_date",
type: FT_DATETIME,
fixed_length: 8,
can_be_null: FALSE
},
{ /* timestamp */
name: "last_update",
type: FT_UINT,
fixed_length: 4,
can_be_null: FALSE
},
{ type: FT_NONE }
}
},
};
#endif
如果需要,可以根據(jù)需要編輯修改include/table_defs.h;然后根據(jù)include/table_defs.h,重新編譯constraints_parser工具:
$ make
gcc -DHAVE_OFFSET64_T -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE=1 -D_LARGEFILE_SOURCE=1 -g -I include -I mysql-source/include -I mysql-source/innobase/include -c tables_dict.c -o lib/tables_dict.o
gcc -DHAVE_OFFSET64_T -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE=1 -D_LARGEFILE_SOURCE=1 -g -I include -I mysql-source/include -I mysql-source/innobase/include -o constraints_parser constraints_parser.c lib/tables_dict.o lib/print_data.o lib/check_data.o lib/libut.a lib/libmystrings.a
gcc -DHAVE_OFFSET64_T -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE=1 -D_LARGEFILE_SOURCE=1 -g -I include -I mysql-source/include -I mysql-source/innobase/include -o page_parser page_parser.c lib/tables_dict.o lib/libut.a
6. 從頁中提取行記錄
6.1 合并頁到一個文件
前面已經(jīng)提到,我們需要恢復(fù)的index ID 0 286,包含數(shù)據(jù)的頁位于pages-1246363747/0-286/ 目錄。
total 120
-rw-r--r-- 1 root root 16384 Jun 30 05:09 1254-00001254.page
-rw-r--r-- 1 root root 16384 Jun 30 05:09 1255-00001255.page
-rw-r--r-- 1 root root 16384 Jun 30 05:09 1256-00001256.page
-rw-r--r-- 1 root root 16384 Jun 30 05:09 1257-00001257.page
-rw-r--r-- 1 root root 16384 Jun 30 05:09 50-00000050.page
-rw-r--r-- 1 root root 16384 Jun 30 05:09 74-00000050.page
輸入以下命令進(jìn)行合并頁:
$ find pages-1246363747/0-286/ -type f -name '*.page' | sort -n | xargs cat > pages-1246363747/0-286/customer_pages_concatenated
生成的結(jié)果文件:pages-1246363747/0-286/customer_pages_concatenated,將作為constraints_parser工具的輸入。
6.2 運行constraints_parser工具
下面到恢復(fù)數(shù)據(jù)最核心的步驟——運行constraints_parser工具以提取行記錄。和page_parser工具一樣,需要通過-5或-4參數(shù)指定InnoDB頁格式(COMPACT/REDUNDANT),-f指定輸入文件。
回到例子中,我們可以這樣運行constraints_parser工具(下面的命令是恢復(fù)一個單一的頁,也可以直接恢復(fù)經(jīng)過6.1步驟合并所有頁之后的文件):
$ ./constraints_parser -5 -f pages-1246363747/0-286/50-00000050.page
輸出結(jié)果中每行包含表名以及表中的各個列。備注:其中可能有正確的行記錄,也可能有不正確的行記錄。官方文檔中這個章節(jié)給出了如何調(diào)整表定義獲取盡可能多的有效數(shù)據(jù),同時過濾掉垃圾行,這里不再詳細(xì)描述。
customer 0 120 "" "" "" 32770 0 "0000-00-00 00:12:80" 0
customer 0 0 "" "" "" 0 0 "9120-22-48 29:44:00" 2
customer 61953 0 "" "" "" 2816 0 "7952-32-67 11:43:49" 0
customer 0 0 "" "" "" 0 0 "0000-00-00 00:00:00" 0
... snip ...
customer 0 0 "" "" "" 0 0 "0000-00-00 00:00:00" 16777728
customer 28262 114 "" "" NULL 25965 117 "4603-91-96 76:21:28" 5111809
customer 0 82 "" "" "" 22867 77 "2775-94-58 03:19:18" 1397573972
customer 2 1 "PATRICIA" "JOHNSON" "PATRICIA.JOHNSON@sakilacustomer.org" 6 1 "2006-02-14 22:04:36" 1140008240
customer 3 1 "LINDA" "WILLIAMS" "LINDA.WILLIAMS@sakilacustomer.org" 7 1 "2006-02-14 22:04:36" 1140008240
customer 4 2 "BARBARA" "JONES" "BARBARA.JONES@sakilacustomer.org" 8 1 "2006-02-14 22:04:36" 1140008240
customer 5 1 "ELIZABETH" "BROWN" "ELIZABETH.BROWN@sakilacustomer.org" 9 1 "2006-02-14 22:04:36" 1140008240
customer 6 2 "JENNIFER" "DAVIS" "JENNIFER.DAVIS@sakilacustomer.org" 10 1 "2006-02-14 22:04:36" 1140008240
customer 7 1 "MARIA" "MILLER" "MARIA.MILLER@sakilacustomer.org" 11 1 "2006-02-14 22:04:36" 1140008240
customer 8 2 "SUSAN" "WILSON" "SUSAN.WILSON@sakilacustomer.org" 12 1 "2006-02-14 22:04:36" 1140008240
customer 9 2 "MARGARET" "MOORE" "MARGARET.MOORE@sakilacustomer.org" 13 1 "2006-02-14 22:04:36" 1140008240
... snip ...
customer 0 0 "" "" "" 0 0 "0000-00-00 00:00:00" 0
customer 0 0 "" "" "" 0 0 "7679-35-98 86:44:53" 720578985
7. 導(dǎo)入數(shù)據(jù)到數(shù)據(jù)庫中
最后,為了完成數(shù)據(jù)恢復(fù),需要將步驟6中constraints_parser工具的輸出結(jié)果,使用LOAD DATA INFILE命令導(dǎo)入到數(shù)據(jù)庫中。命令如下:
LOAD DATA INFILE '/tmp/customer_data.tsv'
REPLACE INTO TABLE customer
FIELDS TERMINATED BY '\t'
OPTIONALLY ENCLOSED BY '"'
LINES STARTING BY 'customer\t'
(customer_id, store_id, first_name, last_name, email,
address_id, active, create_date, @last_update)
SET last_update = FROM_UNIXTIME(@last_update);
至此,完成了數(shù)據(jù)的恢復(fù)和導(dǎo)入過程。希望大家不會有機會去實踐這篇文章介紹的方法。
以下內(nèi)容大部分參考自:Percona Data Recovery Tool for InnoDB,文檔是英文的,而且寫的比較晦澀,這里是個人的實戰(zhàn)經(jīng)驗總結(jié),供大家參考學(xué)習(xí)。
在介紹innodb-tools工具進(jìn)行數(shù)據(jù)恢復(fù)之前,首先明確以下幾點:
1、這個工具只能對InnoDB/XtraDB表有效,而無法恢復(fù)MyISAM表(注: Percona號稱有一套用于恢復(fù)MyISAM表的工具,但是本人未做嘗試)。
2、這個工具是以保存的MySQL數(shù)據(jù)文件進(jìn)行恢復(fù)的,而不用MySQL Server運行。
3、不能保證數(shù)據(jù)總一定可被恢復(fù)。例如,被重寫的數(shù)據(jù)不能被恢復(fù),這種情況下可能需要針對系統(tǒng)或物理的方式來恢復(fù),不屬于本工具的范疇。
4、恢復(fù)的最好時機是當(dāng)你發(fā)現(xiàn)數(shù)據(jù)丟失時,盡快備份MySQL數(shù)據(jù)文件。
5、使用這個工具需要手動做一些工作,并不是全自動完成的。
6、恢復(fù)過程依賴于你對丟失數(shù)據(jù)的了解程度,在恢復(fù)過程中可能需要在不同版本的數(shù)據(jù)之間做出選擇。那么如果你越了解自己的數(shù)據(jù),恢復(fù)的可能性就越大。
接下來,下面通過一個例子來介紹如何通過這個工具進(jìn)行恢復(fù)。
1. 前提條件
首先,需要理解的是innodb-tools工具不是通過連接到在線的database進(jìn)行數(shù)據(jù)恢復(fù),而是通過離線拷貝數(shù)據(jù)的方式進(jìn)行的。注意:不要在MySQL運行的時候,直接拷貝InnoDB文件,這樣是不安全的,會影響數(shù)據(jù)恢復(fù)過程。
為了完成數(shù)據(jù)恢復(fù),必須知道將要被恢復(fù)的表結(jié)構(gòu)(列名、數(shù)據(jù)類型)。最簡單的方式就是SHOW CREATE TABLE,當(dāng)然后續(xù)會介紹幾種可替代的方式。因此,如果有一個MySQL server作為備份,即使數(shù)據(jù)是很早的甚至表中沒有記錄,可以有助于使用innodb-tools工具進(jìn)行恢復(fù)。不過這個不是必須的。
2. 簡單例子
復(fù)制代碼 代碼如下:
mysql> TRUNCATE TABLE customer;
3. 構(gòu)建工具
為了構(gòu)建innodb-tools工具,需要依賴于C編譯器、make工具等。
1、下載解壓innodb-tools工具源碼:
復(fù)制代碼 代碼如下:
wget https://launchpad.net/percona-data-recovery-tool-for-innodb/trunk/release-0.5/+download/percona-data-recovery-tool-for-innodb-0.5.tar.gztar -zxvf percona-data-recovery-tool-for-innodb-0.5.tar.gz
2、進(jìn)入解壓后根目錄下的mysql-source目錄,運行配置命令(注:不運行make命令):
復(fù)制代碼 代碼如下:
cd percona-data-recovery-tool-for-innodb-0.5/mysql-source
./configure
3、完成配置步驟后,回到解壓后的根目錄,運行make命令,編譯生成page_parser和constraints_parser工具:
復(fù)制代碼 代碼如下:
cd ..
make
page_parser工具將根據(jù)InnoDB的底層實現(xiàn)原理,解析表的頁和行結(jié)構(gòu)。constraints_parser工具暫時不使用,后續(xù)還需要在定義表結(jié)構(gòu)之后,重新編譯生成它。
如果編譯過程中出現(xiàn)問題,點擊這里。本文使用過程中沒有出現(xiàn)問題,故不再一一列舉。
4. 提取需要的頁
InnoDB頁的默認(rèn)大小是16K,每個頁屬于一個特定表中的一個特定的index。page_parser工具通過讀取數(shù)據(jù)文件,根據(jù)頁頭中的index ID,拷貝每個頁到一個單獨的文件中。
如果你的MySQL server被配置為innodb_file_per_table=1,那么系統(tǒng)已經(jīng)幫你實現(xiàn)上述過程。所有需要的頁都在.ibd文件,而且通常你不需要再切分它。然而,如果.ibd文件中可能包含多個index,那么將頁單獨切分開還是有必要的。如果MySQL server沒有配置innodb_file_per_table,那么數(shù)據(jù)會被保存在一個全局的表命名空間(通常是一個名為ibdata1的文件,本文屬于這種情況),這時候就需要按頁對文件進(jìn)行切分。
4.1 切分頁
運行page_parser工具進(jìn)行切分:
•如果MySQL是5.0之前的版本,InnoDB采取的是REDUNDANT格式,運行以下命令:
復(fù)制代碼 代碼如下:
./page_parser -4 -f /path/to/ibdata1
•如果MySQL是5.0版本,InnoDB采取的是COMPACT格式,運行以下命令:
復(fù)制代碼 代碼如下:
./page_parser -5 -f /path/to/ibdata1
運行后,page_parser工具會創(chuàng)建一個pages-<TIMESTAMP>的目錄,其中TIMESTAMP是UNIX系統(tǒng)時間戳。在這個目錄下,為每個index ID,以頁的index ID創(chuàng)建一個子目錄。例如:
復(fù)制代碼 代碼如下:
pages-1330842944/FIL_PAGE_INDEX/0-1/1-00000008.page
pages-1330842944/FIL_PAGE_INDEX/0-1/6-00000008.page
4.2 選擇需要的Index ID
一般來說,我們需要根據(jù)表的主鍵(PRIMARY index)進(jìn)行恢復(fù),主鍵中包含了所有的行。以下是一些可以實現(xiàn)的步驟:
如果數(shù)據(jù)庫仍處于運行狀態(tài),并且表沒有被drop掉,那么可以啟動InnoDB Tablespace Monitor,輸出所有表和indexes,index IDs到MySQL server的錯誤日志文件。創(chuàng)建innodb_table_monitor表用于收集innodb存儲引擎表及其索引的存儲方式:
復(fù)制代碼 代碼如下:
mysql> CREATE TABLE innodb_table_monitor (id int) ENGINE=InnoDB;
如果innodb_table_monitor已經(jīng)存在,drop表然后重新create表。等MySQL錯誤日志輸出后,可以drop掉這張表以停止打印輸出更多的監(jiān)控。一個輸出的例子如下:
復(fù)制代碼 代碼如下:
TABLE: name sakila/customer, id 0 142, columns 13, indexes 4, appr.rows 0
COLUMNS: customer_id: DATA_INT len 2 prec 0; store_id: DATA_INT len 1 prec 0; first_name: type 12 len 135 prec 0; last_name: type 12 len 135 prec 0; email:
type 12 len 150 prec 0; address_id: DATA_INT len 2 prec 0; active: DATA_INT len 1 prec 0; create_date: DATA_INT len 8 prec 0; last_update: DATA_INT len 4 pr
ec 0; DB_ROW_ID: DATA_SYS prtype 256 len 6 prec 0; DB_TRX_ID: DATA_SYS prtype 257 len 6 prec 0; DB_ROLL_PTR: DATA_SYS prtype 258 len 7 prec 0;
INDEX: name PRIMARY, id 0 286, fields 1/11, type 3
root page 50, appr.key vals 0, leaf pages 1, size pages 1
FIELDS: customer_id DB_TRX_ID DB_ROLL_PTR store_id first_name last_name email address_id active create_date last_update
INDEX: name idx_fk_store_id, id 0 287, fields 1/2, type 0
root page 56, appr.key vals 0, leaf pages 1, size pages 1
FIELDS: store_id customer_id
INDEX: name idx_fk_address_id, id 0 288, fields 1/2, type 0
root page 63, appr.key vals 0, leaf pages 1, size pages 1
FIELDS: address_id customer_id
INDEX: name idx_last_name, id 0 289, fields 1/2, type 0
root page 1493, appr.key vals 0, leaf pages 1, size pages 1
FIELDS: last_name customer_id
這里,我們恢復(fù)的是sakila庫下的customer表,從上面可以獲取其主鍵信息:
復(fù)制代碼 代碼如下:
INDEX: name PRIMARY, id 0 286, fields 1/11, type 3
Index ID是0 256,因此我們需要恢復(fù)的InnoDB頁位于0-256子目錄下。
備注:參考文檔原文中之描述了以上這種獲取表的index ID的方法,本文在實際操作中,采取了更簡單的一種方式,即直接恢復(fù)page_parser生成的所有InnoDB頁。實踐證明這種方法也是可行的:)
5. 生成表定義
步驟4中,我們已經(jīng)找到了需要的數(shù)據(jù),接下來需要找到表結(jié)構(gòu),創(chuàng)建表定義,將其編譯到constraints_parser中,然后使用這個工具從InnoDB頁中提取表中的行。
表定義包含了表中的列、列順序、數(shù)據(jù)類型。如果MySQL server仍處于運行且表未被drop掉,那么簡單實用SHOW CREATE TABLE就可以收集到這些信息。接下來將使用這些表結(jié)構(gòu)信息來創(chuàng)建一個C結(jié)構(gòu)體標(biāo)識的表定義,然后編譯到constraints_parser工具。C結(jié)構(gòu)體的定義存放在include/table_defs.h中。
最簡單的方式是create_defs.pl Perl 腳本,連接到MySQL server,讀取SHOW CREATE TABLE的結(jié)果,輸出生成的表定義到標(biāo)準(zhǔn)輸出。下面是個例子,其中直接將結(jié)果重定向到了include/table_defs.h中:
If possible, the easiest way to create the table definition is with the create_defs.pl Perl script. It connects to the MySQL server and reads SHOW CREATE TABLE output, and prints the generated definition to its standard output. Here is an example:
復(fù)制代碼 代碼如下:
$ ./create_defs.pl --host=localhost --user=root --password=123456 --db=sakila --table=customer > include/table_defs.h
下面是例子中的表結(jié)構(gòu):
復(fù)制代碼 代碼如下:
CREATE TABLE `customer` (
`customer_id` smallint(5) UNSIGNED NOT NULL AUTO_INCREMENT,
`store_id` tinyint(3) UNSIGNED NOT NULL,
`first_name` varchar(45) NOT NULL,
`last_name` varchar(45) NOT NULL,
`email` varchar(50) DEFAULT NULL,
`address_id` smallint(5) UNSIGNED NOT NULL,
`active` tinyint(1) NOT NULL DEFAULT '1',
`create_date` datetime NOT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`customer_id`),
KEY `idx_fk_store_id` (`store_id`),
KEY `idx_fk_address_id` (`address_id`),
KEY `idx_last_name` (`last_name`),
CONSTRAINT `fk_customer_address` FOREIGN KEY (`address_id`) REFERENCES `address` (`address_id`) ON UPDATE CASCADE,
CONSTRAINT `fk_customer_store` FOREIGN KEY (`store_id`) REFERENCES `store` (`store_id`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
下面是生成的表定義:
復(fù)制代碼 代碼如下:
#ifndef table_defs_h
#define table_defs_h
// Table definitions
table_def_t table_definitions[] = {
{
name: "customer",
{
{ /* smallint(5) unsigned */
name: "customer_id",
type: FT_UINT,
fixed_length: 2,
has_limits: TRUE,
limits: {
can_be_null: FALSE,
uint_min_val: 0,
uint_max_val: 65535
},
can_be_null: FALSE
},
{ /* Innodb's internally used field */
name: "DB_TRX_ID",
type: FT_INTERNAL,
fixed_length: 6,
can_be_null: FALSE
},
{ /* Innodb's internally used field */
name: "DB_ROLL_PTR",
type: FT_INTERNAL,
fixed_length: 7,
can_be_null: FALSE
},
{ /* tinyint(3) unsigned */
name: "store_id",
type: FT_UINT,
fixed_length: 1,
has_limits: TRUE,
limits: {
can_be_null: FALSE,
uint_min_val: 0,
uint_max_val: 255
},
can_be_null: FALSE
},
{ /* varchar(45) */
name: "first_name",
type: FT_CHAR,
min_length: 0,
max_length: 45,
has_limits: TRUE,
limits: {
can_be_null: FALSE,
char_min_len: 0,
char_max_len: 45,
char_ascii_only: TRUE
},
can_be_null: FALSE
},
{ /* varchar(45) */
name: "last_name",
type: FT_CHAR,
min_length: 0,
max_length: 45,
has_limits: TRUE,
limits: {
can_be_null: FALSE,
char_min_len: 0,
char_max_len: 45,
char_ascii_only: TRUE
},
can_be_null: FALSE
},
{ /* varchar(50) */
name: "email",
type: FT_CHAR,
min_length: 0,
max_length: 50,
has_limits: TRUE,
limits: {
can_be_null: TRUE,
char_min_len: 0,
char_max_len: 50,
char_ascii_only: TRUE
},
can_be_null: TRUE
},
{ /* smallint(5) unsigned */
name: "address_id",
type: FT_UINT,
fixed_length: 2,
has_limits: TRUE,
limits: {
can_be_null: FALSE,
uint_min_val: 0,
uint_max_val: 65535
},
can_be_null: FALSE
},
{ /* tinyint(1) */
name: "active",
type: FT_INT,
fixed_length: 1,
can_be_null: FALSE
},
{ /* datetime */
name: "create_date",
type: FT_DATETIME,
fixed_length: 8,
can_be_null: FALSE
},
{ /* timestamp */
name: "last_update",
type: FT_UINT,
fixed_length: 4,
can_be_null: FALSE
},
{ type: FT_NONE }
}
},
};
#endif
如果需要,可以根據(jù)需要編輯修改include/table_defs.h;然后根據(jù)include/table_defs.h,重新編譯constraints_parser工具:
復(fù)制代碼 代碼如下:
$ make
gcc -DHAVE_OFFSET64_T -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE=1 -D_LARGEFILE_SOURCE=1 -g -I include -I mysql-source/include -I mysql-source/innobase/include -c tables_dict.c -o lib/tables_dict.o
gcc -DHAVE_OFFSET64_T -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE=1 -D_LARGEFILE_SOURCE=1 -g -I include -I mysql-source/include -I mysql-source/innobase/include -o constraints_parser constraints_parser.c lib/tables_dict.o lib/print_data.o lib/check_data.o lib/libut.a lib/libmystrings.a
gcc -DHAVE_OFFSET64_T -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE=1 -D_LARGEFILE_SOURCE=1 -g -I include -I mysql-source/include -I mysql-source/innobase/include -o page_parser page_parser.c lib/tables_dict.o lib/libut.a
6. 從頁中提取行記錄
6.1 合并頁到一個文件
前面已經(jīng)提到,我們需要恢復(fù)的index ID 0 286,包含數(shù)據(jù)的頁位于pages-1246363747/0-286/ 目錄。
復(fù)制代碼 代碼如下:
total 120
-rw-r--r-- 1 root root 16384 Jun 30 05:09 1254-00001254.page
-rw-r--r-- 1 root root 16384 Jun 30 05:09 1255-00001255.page
-rw-r--r-- 1 root root 16384 Jun 30 05:09 1256-00001256.page
-rw-r--r-- 1 root root 16384 Jun 30 05:09 1257-00001257.page
-rw-r--r-- 1 root root 16384 Jun 30 05:09 50-00000050.page
-rw-r--r-- 1 root root 16384 Jun 30 05:09 74-00000050.page
輸入以下命令進(jìn)行合并頁:
復(fù)制代碼 代碼如下:
$ find pages-1246363747/0-286/ -type f -name '*.page' | sort -n | xargs cat > pages-1246363747/0-286/customer_pages_concatenated
生成的結(jié)果文件:pages-1246363747/0-286/customer_pages_concatenated,將作為constraints_parser工具的輸入。
6.2 運行constraints_parser工具
下面到恢復(fù)數(shù)據(jù)最核心的步驟——運行constraints_parser工具以提取行記錄。和page_parser工具一樣,需要通過-5或-4參數(shù)指定InnoDB頁格式(COMPACT/REDUNDANT),-f指定輸入文件。
回到例子中,我們可以這樣運行constraints_parser工具(下面的命令是恢復(fù)一個單一的頁,也可以直接恢復(fù)經(jīng)過6.1步驟合并所有頁之后的文件):
復(fù)制代碼 代碼如下:
$ ./constraints_parser -5 -f pages-1246363747/0-286/50-00000050.page
輸出結(jié)果中每行包含表名以及表中的各個列。備注:其中可能有正確的行記錄,也可能有不正確的行記錄。官方文檔中這個章節(jié)給出了如何調(diào)整表定義獲取盡可能多的有效數(shù)據(jù),同時過濾掉垃圾行,這里不再詳細(xì)描述。
復(fù)制代碼 代碼如下:
customer 0 120 "" "" "" 32770 0 "0000-00-00 00:12:80" 0
customer 0 0 "" "" "" 0 0 "9120-22-48 29:44:00" 2
customer 61953 0 "" "" "" 2816 0 "7952-32-67 11:43:49" 0
customer 0 0 "" "" "" 0 0 "0000-00-00 00:00:00" 0
... snip ...
customer 0 0 "" "" "" 0 0 "0000-00-00 00:00:00" 16777728
customer 28262 114 "" "" NULL 25965 117 "4603-91-96 76:21:28" 5111809
customer 0 82 "" "" "" 22867 77 "2775-94-58 03:19:18" 1397573972
customer 2 1 "PATRICIA" "JOHNSON" "PATRICIA.JOHNSON@sakilacustomer.org" 6 1 "2006-02-14 22:04:36" 1140008240
customer 3 1 "LINDA" "WILLIAMS" "LINDA.WILLIAMS@sakilacustomer.org" 7 1 "2006-02-14 22:04:36" 1140008240
customer 4 2 "BARBARA" "JONES" "BARBARA.JONES@sakilacustomer.org" 8 1 "2006-02-14 22:04:36" 1140008240
customer 5 1 "ELIZABETH" "BROWN" "ELIZABETH.BROWN@sakilacustomer.org" 9 1 "2006-02-14 22:04:36" 1140008240
customer 6 2 "JENNIFER" "DAVIS" "JENNIFER.DAVIS@sakilacustomer.org" 10 1 "2006-02-14 22:04:36" 1140008240
customer 7 1 "MARIA" "MILLER" "MARIA.MILLER@sakilacustomer.org" 11 1 "2006-02-14 22:04:36" 1140008240
customer 8 2 "SUSAN" "WILSON" "SUSAN.WILSON@sakilacustomer.org" 12 1 "2006-02-14 22:04:36" 1140008240
customer 9 2 "MARGARET" "MOORE" "MARGARET.MOORE@sakilacustomer.org" 13 1 "2006-02-14 22:04:36" 1140008240
... snip ...
customer 0 0 "" "" "" 0 0 "0000-00-00 00:00:00" 0
customer 0 0 "" "" "" 0 0 "7679-35-98 86:44:53" 720578985
7. 導(dǎo)入數(shù)據(jù)到數(shù)據(jù)庫中
最后,為了完成數(shù)據(jù)恢復(fù),需要將步驟6中constraints_parser工具的輸出結(jié)果,使用LOAD DATA INFILE命令導(dǎo)入到數(shù)據(jù)庫中。命令如下:
復(fù)制代碼 代碼如下:
LOAD DATA INFILE '/tmp/customer_data.tsv'
REPLACE INTO TABLE customer
FIELDS TERMINATED BY '\t'
OPTIONALLY ENCLOSED BY '"'
LINES STARTING BY 'customer\t'
(customer_id, store_id, first_name, last_name, email,
address_id, active, create_date, @last_update)
SET last_update = FROM_UNIXTIME(@last_update);
至此,完成了數(shù)據(jù)的恢復(fù)和導(dǎo)入過程。希望大家不會有機會去實踐這篇文章介紹的方法。
您可能感興趣的文章:
- MySQL數(shù)據(jù)庫InnoDB引擎下服務(wù)器斷電數(shù)據(jù)恢復(fù)方法
- MySQL數(shù)據(jù)庫遭到攻擊篡改(使用備份和binlog進(jìn)行數(shù)據(jù)恢復(fù))
- MySQL中truncate誤操作后的數(shù)據(jù)恢復(fù)案例
- 修改MySQL的數(shù)據(jù)庫引擎為INNODB的方法
- 修改Innodb的數(shù)據(jù)頁大小以優(yōu)化MySQL的方法
- Mysql InnoDB刪除數(shù)據(jù)后釋放磁盤空間的方法
- MySQL數(shù)據(jù)庫InnoDB引擎主從復(fù)制同步經(jīng)驗總結(jié)
- MySQL數(shù)據(jù)庫修復(fù)方法(MyISAM/InnoDB)
- MySQL數(shù)據(jù)庫INNODB表損壞修復(fù)處理過程分享
- 深入探討:MySQL數(shù)據(jù)庫MyISAM與InnoDB存儲引擎的比較
- MySQL InnoDB和MyISAM數(shù)據(jù)引擎的差別分析
- MySQL異?;謴?fù)之無主鍵情況下innodb數(shù)據(jù)恢復(fù)的方法
相關(guān)文章
mysql id從1開始自增 快速解決id不連續(xù)的問題
這篇文章主要介紹了mysql id從1開始自增 快速解決id不連續(xù)的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07mysql中數(shù)據(jù)庫與數(shù)據(jù)表編碼格式的查看、創(chuàng)建及修改
這篇文章給大家介紹了如何查看、創(chuàng)建以及修改數(shù)據(jù)庫與數(shù)據(jù)表的編碼格式,另外還給大家分享了添加和刪除外鍵的示例代碼,文中介紹的很詳細(xì),對大家的理解和學(xué)習(xí)具有一定的參考借鑒價值,有需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧。2016-11-11千萬級用戶系統(tǒng)SQL調(diào)優(yōu)實戰(zhàn)分享
這篇文章主要介紹了千萬級用戶系統(tǒng)SQL調(diào)優(yōu)實戰(zhàn)分享,用戶日活百萬級,注冊用戶千萬級,而且若還沒有進(jìn)行分庫分表,則該DB里的用戶表可能就一張,單表上千萬的用戶數(shù)據(jù),下面我們就來學(xué)習(xí)如何讓優(yōu)化,需要的朋友可以參考一下2022-03-03deepin 2014系統(tǒng)下安裝mysql數(shù)據(jù)庫的方法步驟
這篇文章主要給大家介紹了在deepin 2014系統(tǒng)下安裝mysql數(shù)據(jù)庫的方法步驟,文中通過圖文介紹的非常詳細(xì),相信對大家具有一定的參考價值,需要的朋友們下面來一起看看吧。2017-04-04percona-toolkit對MySQL的復(fù)制和監(jiān)控類操作教程
這篇文章主要介紹了使用percona-toolkit對MySQL進(jìn)行復(fù)制和監(jiān)控類操作的教程,percona-toolkit是一款強大的MySQL輔助軟件,需要的朋友可以參考下2015-11-11MYSQL安裝時解決要輸入current root password的解決方法
在裝MYSQL的時候發(fā)現(xiàn)要輸入current root password不記得以前在電腦里裝過(你的系統(tǒng)曾經(jīng)裝過MYSQL在重裝就會要求輸入原來設(shè)定的密碼,如果是第一次安裝就不會出現(xiàn)),在網(wǎng)上苦苦搜尋解決方法。2011-07-07