首页 数据库 mysql教程 MySQL数据库InnoDB数据恢复工具的使用小结详解_MySQL

MySQL数据库InnoDB数据恢复工具的使用小结详解_MySQL

Jun 01, 2016 pm 01:24 PM
数据库 数据恢复 记录

bitsCN.com 本文从实际使用经验出发,介绍一款开源的MySQL数据库InnoDB数据恢复工具:innodb-tools,它通过从原始数据文件中提取表的行记录,实现从丢失的或者被毁坏的MySQL表中恢复数据。例如,当你不小心执行DROP TABLE、TRUNCATE TABLE或者DROP DATABASE之后,可以通过以下方式恢复数据。
以下内容大部分参考自:Percona Data Recovery Tool for InnoDB,文档是英文的,而且写的比较晦涩,这里是个人的实战经验总结,供大家参考学习。
在介绍innodb-tools工具进行数据恢复之前,首先明确以下几点:
1、这个工具只能对InnoDB/XtraDB表有效,而无法恢复MyISAM表(注: Percona号称有一套用于恢复MyISAM表的工具,但是本人未做尝试)。
2、这个工具是以保存的MySQL数据文件进行恢复的,而不用MySQL Server运行。
3、不能保证数据总一定可被恢复。例如,被重写的数据不能被恢复,这种情况下可能需要针对系统或物理的方式来恢复,不属于本工具的范畴。
4、恢复的最好时机是当你发现数据丢失时,尽快备份MySQL数据文件。
5、使用这个工具需要手动做一些工作,并不是全自动完成的。
6、恢复过程依赖于你对丢失数据的了解程度,在恢复过程中可能需要在不同版本的数据之间做出选择。那么如果你越了解自己的数据,恢复的可能性就越大。
接下来,下面通过一个例子来介绍如何通过这个工具进行恢复。
1. 前提条件
首先,需要理解的是innodb-tools工具不是通过连接到在线的database进行数据恢复,而是通过离线拷贝数据的方式进行的。注意:不要在MySQL运行的时候,直接拷贝InnoDB文件,这样是不安全的,会影响数据恢复过程。
为了完成数据恢复,必须知道将要被恢复的表结构(列名、数据类型)。最简单的方式就是SHOW CREATE TABLE,当然后续会介绍几种可替代的方式。因此,如果有一个MySQL server作为备份,即使数据是很早的甚至表中没有记录,可以有助于使用innodb-tools工具进行恢复。不过这个不是必须的。
2. 简单例子

mysql> TRUNCATE TABLE customer;

3. 构建工具
为了构建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、进入解压后根目录下的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工具将根据InnoDB的底层实现原理,解析表的页和行结构。constraints_parser工具暂时不使用,后续还需要在定义表结构之后,重新编译生成它。
如果编译过程中出现问题,点击这里。本文使用过程中没有出现问题,故不再一一列举。
4. 提取需要的页
InnoDB页的默认大小是16K,每个页属于一个特定表中的一个特定的index。page_parser工具通过读取数据文件,根据页头中的index ID,拷贝每个页到一个单独的文件中。
如果你的MySQL server被配置为innodb_file_per_table=1,那么系统已经帮你实现上述过程。所有需要的页都在.ibd文件,而且通常你不需要再切分它。然而,如果.ibd文件中可能包含多个index,那么将页单独切分开还是有必要的。如果MySQL server没有配置innodb_file_per_table,那么数据会被保存在一个全局的表命名空间(通常是一个名为ibdata1的文件,本文属于这种情况),这时候就需要按页对文件进行切分。
4.1 切分页
运行page_parser工具进行切分:
•如果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工具会创建一个pages-的目录,其中TIMESTAMP是UNIX系统时间戳。在这个目录下,为每个index ID,以页的index ID创建一个子目录。例如:

pages-1330842944/FIL_PAGE_INDEX/0-1/1-00000008.page
pages-1330842944/FIL_PAGE_INDEX/0-1/6-00000008.page

4.2 选择需要的Index ID
一般来说,我们需要根据表的主键(PRIMARY index)进行恢复,主键中包含了所有的行。以下是一些可以实现的步骤:
如果数据库仍处于运行状态,并且表没有被drop掉,那么可以启动InnoDB Tablespace Monitor,输出所有表和indexes,index IDs到MySQL server的错误日志文件。创建innodb_table_monitor表用于收集innodb存储引擎表及其索引的存储方式:

mysql> CREATE TABLE innodb_table_monitor (id int) ENGINE=InnoDB;

如果innodb_table_monitor已经存在,drop表然后重新create表。等MySQL错误日志输出后,可以drop掉这张表以停止打印输出更多的监控。一个输出的例子如下:

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

这里,我们恢复的是sakila库下的customer表,从上面可以获取其主键信息:

INDEX: name PRIMARY, id 0 286, fields 1/11, type 3

Index ID是0 256,因此我们需要恢复的InnoDB页位于0-256子目录下。
备注:参考文档原文中之描述了以上这种获取表的index ID的方法,本文在实际操作中,采取了更简单的一种方式,即直接恢复page_parser生成的所有InnoDB页。实践证明这种方法也是可行的:)
5. 生成表定义
步骤4中,我们已经找到了需要的数据,接下来需要找到表结构,创建表定义,将其编译到constraints_parser中,然后使用这个工具从InnoDB页中提取表中的行。
表定义包含了表中的列、列顺序、数据类型。如果MySQL server仍处于运行且表未被drop掉,那么简单实用SHOW CREATE TABLE就可以收集到这些信息。接下来将使用这些表结构信息来创建一个C结构体标识的表定义,然后编译到constraints_parser工具。C结构体的定义存放在include/table_defs.h中。
最简单的方式是create_defs.pl Perl 脚本,连接到MySQL server,读取SHOW CREATE TABLE的结果,输出生成的表定义到标准输出。下面是个例子,其中直接将结果重定向到了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

下面是例子中的表结构:

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

如果需要,可以根据需要编辑修改include/table_defs.h;然后根据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 合并页到一个文件
前面已经提到,我们需要恢复的index ID 0 286,包含数据的页位于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

输入以下命令进行合并页:

$ find pages-1246363747/0-286/ -type f -name '*.page' | sort -n | xargs cat > pages-1246363747/0-286/customer_pages_concatenated

生成的结果文件:pages-1246363747/0-286/customer_pages_concatenated,将作为constraints_parser工具的输入。
6.2 运行constraints_parser工具
下面到恢复数据最核心的步骤――运行constraints_parser工具以提取行记录。和page_parser工具一样,需要通过-5或-4参数指定InnoDB页格式(COMPACT/REDUNDANT),-f指定输入文件。
回到例子中,我们可以这样运行constraints_parser工具(下面的命令是恢复一个单一的页,也可以直接恢复经过6.1步骤合并所有页之后的文件):

$ ./constraints_parser -5 -f pages-1246363747/0-286/50-00000050.page

输出结果中每行包含表名以及表中的各个列。备注:其中可能有正确的行记录,也可能有不正确的行记录。官方文档中这个章节给出了如何调整表定义获取尽可能多的有效数据,同时过滤掉垃圾行,这里不再详细描述。

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. 导入数据到数据库中
最后,为了完成数据恢复,需要将步骤6中constraints_parser工具的输出结果,使用LOAD DATA INFILE命令导入到数据库中。命令如下:

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); 

至此,完成了数据的恢复和导入过程。希望大家不会有机会去实践这篇文章介绍的方法。
bitsCN.com

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

<🎜>:泡泡胶模拟器无穷大 - 如何获取和使用皇家钥匙
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系统,解释
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆树的耳语 - 如何解锁抓钩
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1667
14
CakePHP 教程
1426
52
Laravel 教程
1328
25
PHP教程
1273
29
C# 教程
1255
24
iOS 18 新增'已恢复”相册功能 可找回丢失或损坏的照片 iOS 18 新增'已恢复”相册功能 可找回丢失或损坏的照片 Jul 18, 2024 am 05:48 AM

苹果公司最新发布的iOS18、iPadOS18以及macOSSequoia系统为Photos应用增添了一项重要功能,旨在帮助用户轻松恢复因各种原因丢失或损坏的照片和视频。这项新功能在Photos应用的"工具"部分引入了一个名为"已恢复"的相册,当用户设备中存在未纳入其照片库的图片或视频时,该相册将自动显示。"已恢复"相册的出现为因数据库损坏、相机应用未正确保存至照片库或第三方应用管理照片库时照片和视频丢失提供了解决方案。用户只需简单几步

如何在PHP中处理数据库连接错误 如何在PHP中处理数据库连接错误 Jun 05, 2024 pm 02:16 PM

PHP中处理数据库连接报错,可以使用以下步骤:使用mysqli_connect_errno()获取错误代码。使用mysqli_connect_error()获取错误消息。通过捕获并记录这些错误信息,可以轻松识别并解决数据库连接问题,确保应用程序的顺畅运行。

在PHP中使用MySQLi建立数据库连接的详尽教程 在PHP中使用MySQLi建立数据库连接的详尽教程 Jun 04, 2024 pm 01:42 PM

如何在PHP中使用MySQLi建立数据库连接:包含MySQLi扩展(require_once)创建连接函数(functionconnect_to_db)调用连接函数($conn=connect_to_db())执行查询($result=$conn->query())关闭连接($conn->close())

如何在 Golang 中将 JSON 数据保存到数据库中? 如何在 Golang 中将 JSON 数据保存到数据库中? Jun 06, 2024 am 11:24 AM

可以通过使用gjson库或json.Unmarshal函数将JSON数据保存到MySQL数据库中。gjson库提供了方便的方法来解析JSON字段,而json.Unmarshal函数需要一个目标类型指针来解组JSON数据。这两种方法都需要准备SQL语句和执行插入操作来将数据持久化到数据库中。

如何在 Golang 中使用数据库回调函数? 如何在 Golang 中使用数据库回调函数? Jun 03, 2024 pm 02:20 PM

在Golang中使用数据库回调函数可以实现:在指定数据库操作完成后执行自定义代码。通过单独的函数添加自定义行为,无需编写额外代码。回调函数可用于插入、更新、删除和查询操作。必须使用sql.Exec、sql.QueryRow或sql.Query函数才能使用回调函数。

如何用 Golang 连接远程数据库? 如何用 Golang 连接远程数据库? Jun 01, 2024 pm 08:31 PM

通过Go标准库database/sql包,可以连接到MySQL、PostgreSQL或SQLite等远程数据库:创建包含数据库连接信息的连接字符串。使用sql.Open()函数打开数据库连接。执行SQL查询和插入操作等数据库操作。使用defer关闭数据库连接以释放资源。

mysql:简单的概念,用于轻松学习 mysql:简单的概念,用于轻松学习 Apr 10, 2025 am 09:29 AM

MySQL是一个开源的关系型数据库管理系统。1)创建数据库和表:使用CREATEDATABASE和CREATETABLE命令。2)基本操作:INSERT、UPDATE、DELETE和SELECT。3)高级操作:JOIN、子查询和事务处理。4)调试技巧:检查语法、数据类型和权限。5)优化建议:使用索引、避免SELECT*和使用事务。

PHP与不同数据库的连接:MySQL、PostgreSQL、Oracle和更多 PHP与不同数据库的连接:MySQL、PostgreSQL、Oracle和更多 Jun 01, 2024 pm 03:02 PM

PHP连接数据库指南:MySQL:安装MySQLi扩展,创建连接(servername、username、password、dbname)。PostgreSQL:安装PgSQL扩展,创建连接(host、dbname、user、password)。Oracle:安装OracleOCI8扩展,创建连接(servername、username、password)。实战案例:获取MySQL数据、PostgreSQL查询、OracleOCI8更新记录。

See all articles