MySQL数据库抢救数据利器ibd2sql详细介绍(附带下载地址)

在这里插入图片描述

简介:

ibd2sql 是一个使用纯python3编写的离线解析MYSQL INNODB存储引擎的ibd文件的工具,并将其转换为 SQL 语句。
无第三方依赖包. 使用GPL-3.0 license.

特点:

1.方便: 提取表DDL
2.实用: 可替换库(--schema)/表(--table)名, 可在sql语句中输出完整的字段(--complete)
3.简单: 纯python3代码编写, 无依赖包.
4.支持众多数据类型: 支持所有mysql数据类型
5.支持复杂的表结构: 分区表, 注释, 主键, 外键, 约束, 自增, 普通索引, 前缀索引, 主键前缀索引, 唯一索引, 复合索引, 默认值, 符号, 虚拟字段, INSTANT, 无主键等情况的表
6.数据误删恢复: 可以输出被标记为deleted的数据
7.安全: 离线解析ibd文件, 仅可读权限即可
8.支持范围广: 支持mysql 5.6 or 5.7 or 8.0 or 8.4 or 9.0

版本更新:

[root@cjc-db-02 ibd2sql-1.10]# cat README_zh.md 
版本   更新时间   说明
v0.1  2023.04.27  第一个版本
v0.2  2023.08.30  支持更多数据类型
v0.3  2023.10.13  支持5.7升级到8.0的ibd文件
v1.0  2024.01.05  支持debug,支持更多类型和功能
v1.1  2024.04.12  修复一些bug
v1.2  2024.04.25  新增空间坐标的支持
v1.3  2024.05.11  支持mysql 5.6, 5.7
v1.4  2024.05.21  支持溢出页, 子分区
v1.5  2024.07.10  vector and instant BUG
v1.6  2024.09.19  修复一些bug
v1.7  2024.10.29  1.修复一些bug 2.支持压缩页 3.支持drop table的恢复 4.ucs2,utf16,utf32 字符集支持             |
v1.8  2024.11.09  1.支持所有字符集 2.支持kering 插件加密的表 3.支持web控制台查看数据结构 4.支持修改lower_case_table_names|
v1.9  2025.02.21  修复已知BUG, 支持直接解析5.7的ibd文件
v1.10 2025.04.16  修复已知BUG, 添加快速统计表行数的脚本

需求与支持:

require: python3
support range: mysql5.x 8.x 9.x

下载和安装:

下载和安装:ibd2sql-1.10.tar.gz

v1.10 下载地址:离线上传到服务器

https://github.com/ddcw/ibd2sql/archive/refs/tags/v1.10.tar.gz

或者如果服务器可以联网,可以直接wget下载到服务器:

wget https://github.com/ddcw/ibd2sql/archive/refs/tags/v1.10.tar.gz

查看 ibd2sql-1.10.tar.gz:

[mysql@cjc-db-02 ibd2sql]$ ls -lrth ibd2sql-1.10.tar.gz 
-rw-r--r-- 1 root root 113K May 31 20:07 ibd2sql-1.10.tar.gz

[mysql@cjc-db-02 ibd2sql]$ md5sum ibd2sql-1.10.tar.gz 
3e73b0bf7ab7b7f65d3ddac413a44163  ibd2sql-1.10.tar.gz

解压:

[root@cjc-db-02 ibd2sql]# tar -zxvf ibd2sql-1.10.tar.gz 

[root@cjc-db-02 ibd2sql-1.10]# ls -lrth
total 164K
-rw-rw-r-- 1 root root  31K Apr 16 18:42 xfs_recovery_v0.3.py
-rw-rw-r-- 1 root root  27K Apr 16 18:42 test.sh
-rw-rw-r-- 1 root root 2.2K Apr 16 18:42 super_fast_count.py
-rw-rw-r-- 1 root root 3.5K Apr 16 18:42 README_zh.md
-rw-rw-r-- 1 root root 3.5K Apr 16 18:42 README.md
-rw-rw-r-- 1 root root 2.6K Apr 16 18:42 modify_lower_case_table_names.py
-rw-rw-r-- 1 root root 8.9K Apr 16 18:42 main.py
-rw-rw-r-- 1 root root  35K Apr 16 18:42 LICENSE
-rw-rw-r-- 1 root root  21K Apr 16 18:42 ibd2sql_web.py
-rw-rw-r-- 1 root root  12K Apr 16 18:42 ibd2sql_mini_for_redundant.py
drwxrwxr-x 2 root root   99 Apr 16 18:42 docs
drwxrwxr-x 3 root root 4.0K May 31 20:35 ibd2sql

下载和安装 Python:

下载地址:

https://www.python.org/downloads/release/python-361/

图片.png
图片.png

上传:

[root@cjc-db-02 ibd2sql]# ls -lrth Python-3.6.1.tgz 
-rw-r--r-- 1 root root 22M May 31 08:44 Python-3.6.1.tgz

[root@cjc-db-02 ibd2sql]# md5sum Python-3.6.1.tgz 
2d0fc9f3a5940707590e07f03ecb08b9  Python-3.6.1.tgz

解压:

tar xvf Python-3.6.1.tgz
cd Python-3.6.1
mkdir -p /usr/local/python3
./configure --prefix=/usr/local/python3
make && make install
ln -s /usr/local/python3/bin/python3 /usr/bin/python3

[root@cjc-db-02 Python-3.6.1]# python3  -V
Python 3.6.1

下载和安装:MySQL 8.4.5

https://dev.mysql.com/downloads/mysql/

图片.png
图片.png

[root@cjc-db-02 mysql]# ls -lrth
total 2.7G
-rw-r--r-- 1 7155 31415 868M Apr  1 18:11 mysql-8.4.5-linux-glibc2.17-x86_64.tar.xz
-rw-r--r-- 1 7155 31415  78M Apr  1 18:25 mysql-router-8.4.5-linux-glibc2.17-x86_64.tar.xz
-rw-r--r-- 1 7155 31415 432M Apr  1 18:44 mysql-test-8.4.5-linux-glibc2.17-x86_64.tar.xz
-rw-r--r-- 1 root root  1.4G May 31 09:51 mysql-8.4.5-linux-glibc2.17-x86_64.tar
[root@cjc-db-02 mysql]# md5sum mysql-*.tar.xz
8c7573c055a38821f8e65946f48a07ae  mysql-8.4.5-linux-glibc2.17-x86_64.tar.xz
55c76f9b10328e821fac1dbc80204a5e  mysql-router-8.4.5-linux-glibc2.17-x86_64.tar.xz

安装过程省略:
和 8.0 相比,已经删除了 relay_log_info_repository 参数,如果my.cnf配置了,需要参数这个参数。

ibd2sql工具测试:

1.ibd2sql常用命令

帮助信息:

[mysql@cjc-db-02 ibd2sql-1.5]$ python3 main.py --help
[root@cjc-db-02 ibd2sql-1.10]# python3 main.py --help
usage: main.py [--help] [--version] [--ddl] [--sql] [--delete]
               [--complete-insert] [--force] [--set] [--multi-value]
               [--replace] [--table TABLE_NAME] [--schema SCHEMA_NAME]
               [--sdi-table SDI_TABLE] [--where-trx WHERE_TRX]
               [--where-rollptr WHERE_ROLLPTR] [--limit LIMIT] [--debug]
               [--debug-file DEBUG_FILE] [--page-min PAGE_MIN]
               [--page-max PAGE_MAX] [--page-start PAGE_START]
               [--page-count PAGE_COUNT] [--page-skip PAGE_SKIP] [--mysql5]
               [--keyring-file KEYRING_FILE]
               [FILENAME]

解析mysql 5.7/8.0的ibd文件 https://github.com/ddcw/ibd2sql

positional arguments:
  FILENAME              ibd filename

optional arguments:
  --help, -h            show help
  --version, -v, -V     show version
  --ddl, -d             print ddl
  --sql                 print data by sql
  --delete              print data only for flag of deleted
  --complete-insert     use complete insert statements for sql
  --force, -f           force pasrser file when Error Page
  --set                 set/enum to fill in actual data instead of strings
  --multi-value         single sql if data belong to one page
  --replace             "REPLACE INTO" replace to "INSERT INTO" (default)
  --table TABLE_NAME    replace table name except ddl
  --schema SCHEMA_NAME  replace table name except ddl
  --sdi-table SDI_TABLE
                        read SDI PAGE from this file(ibd)(partition table)
  --where-trx WHERE_TRX
                        default (0,281474976710656)
  --where-rollptr WHERE_ROLLPTR
                        default (0,72057594037927936)
  --limit LIMIT         limit rows
  --debug, -D           will DEBUG (it's too big)
  --debug-file DEBUG_FILE
                        default sys.stdout if DEBUG
  --page-min PAGE_MIN   if PAGE NO less than it, will break
  --page-max PAGE_MAX   if PAGE NO great than it, will break
  --page-start PAGE_START
                        INDEX PAGE START NO
  --page-count PAGE_COUNT
                        page count NO
  --page-skip PAGE_SKIP
                        skip some pages when start parse index page
  --mysql5              for mysql5.7 flag
  --keyring-file KEYRING_FILE, -k KEYRING_FILE
                        keyring filename
Example:
ibd2sql /data/db1/xxx.ibd --ddl --sql
ibd2sql /data/db1/xxx.ibd --delete --sql
ibd2sql /data/db1/xxx#p#p1.ibd --sdi-table /data/db1/xxx#p#p0.ibd --sql
ibd2sql /mysql57/db1/xxx.ibd --sdi-table /mysql80/db1/xxx.ibd --sql --mysql5

常用命令:

--version, -v, -V: 显示版本信息。
--ddl, -d: 打印 DDL。
--sql: 打印数据的 SQL 语句。
--delete: 仅打印被标记为已删除的数据。
--complete-insert: 使用完整的插入语句。
--force, -f: 在遇到错误页面时强制解析文件。
--set: 用实际数据填充 set/enum 而不是字符串。
--multi-value: 如果数据属于同一页面,则使用单个 SQL 语句。
--replace: 使用 "REPLACE INTO" 替换 "INSERT INTO"(默认)。
--table TABLE_NAME: 替换表名(不包括 DDL)。
--schema SCHEMA_NAME: 替换库名(不包括 DDL)。
--sdi-table SDI_TABLE: 从这个 IBD 文件(分区表)读取 SDI 页面。
--where-trx WHERE_TRX: 事务过滤范围,默认 (0,281474976710656)。
--where-rollptr WHERE_ROLLPTR: 回滚指针过滤范围,默认 (0,72057594037927936)。
--limit LIMIT: 限制行数。
--debug, -D: 调试模式(输出非常大)。
--debug-file DEBUG_FILE: 调试输出文件,默认为 sys.stdout。
--page-min PAGE_MIN: 如果页面编号小于此值,则终止。
--page-max PAGE_MAX: 如果页面编号大于此值,则终止。
--page-start PAGE_START: 索引页面起始编号。
--page-count PAGE_COUNT: 页面计数编号。
--page-skip PAGE_SKIP: 解析索引页面时跳过某些页面。
--mysql5: 适用于 MySQL 5.7 标志。

2.准备测试数据

创建一张小表cjc.t1和一张大表cjc.sales_order;

mysql> create database cjc;
mysql> use cjc;
create table t1(id int primary key,name varchar(10),time time);
insert into t1 values(1,'x',now());
insert into t1 values(2,'y',now());
insert into t1 values(3,'z',now());
insert into t1 values(4,'u',now());
insert into t1 values(5,'v',now());

mysql> select * from cjc.t1;
+----+------+----------+
| id | name | time     |
+----+------+----------+
|  1 | x    | 10:35:52 |
|  2 | y    | 10:35:56 |
|  3 | z    | 10:36:02 |
|  4 | u    | 10:36:07 |
|  5 | v    | 10:36:16 |
+----+------+----------+
5 rows in set (0.00 sec)


CREATE TABLE sales_order (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    order_no VARCHAR(50) NOT NULL UNIQUE,
    customer_id INT NOT NULL,
    product_id INT NOT NULL,
    quantity INT NOT NULL,
    price DECIMAL(10,2) NOT NULL,
    total_amount DECIMAL(10,2) GENERATED ALWAYS AS (quantity * price) STORED,
    order_date DATE NOT NULL,
    status ENUM('pending', 'processing', 'shipped', 'completed', 'cancelled') NOT NULL,
    INDEX idx_customer (customer_id),
    INDEX idx_date (order_date)
) ENGINE=InnoDB;

创建存储过程:

DELIMITER $$

CREATE PROCEDURE GenerateSalesOrders()
BEGIN
    DECLARE counter INT DEFAULT 0;
    DECLARE batchSize INT DEFAULT 1000;  -- 每批插入1000条
    DECLARE maxRecords INT DEFAULT 1000000;
    
    WHILE counter < maxRecords DO
        INSERT INTO sales_order (
            order_no, 
            customer_id, 
            product_id, 
            quantity, 
            price, 
            order_date, 
            status
        )
        SELECT
            CONCAT('ORD', LPAD(counter + n, 10, '0')),  -- 唯一订单号
            FLOOR(1 + RAND() * 1000),                   -- 客户ID 1-1000
            FLOOR(1 + RAND() * 500),                   -- 产品ID 1-500
            FLOOR(1 + RAND() * 10),                    -- 数量 1-10
            ROUND(10 + RAND() * 990, 2),               -- 价格 10.00-999.99
            DATE_ADD('2020-01-01', INTERVAL FLOOR(RAND() * 2000) DAY),  -- 日期 2020-2025
            ELT(FLOOR(1 + RAND() * 5), 'pending', 'processing', 'shipped', 'completed', 'cancelled') -- 状态
        FROM (
            SELECT a.N + b.N * 10 + c.N * 100 AS n
            FROM 
                (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) a,
                (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) b,
                (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) c
            ORDER BY n
            LIMIT batchSize
        ) numbers;
        
        SET counter = counter + batchSize;
        COMMIT;
        SELECT CONCAT('Inserted: ', counter, '/1000000') AS progress;
    END WHILE;
END$$

DELIMITER ;

调用存储过程(耗时约5-15分钟)

CALL GenerateSalesOrders();
......
+--------------------------+
| progress                 |
+--------------------------+
| Inserted: 998000/1000000 |
+--------------------------+
1 row in set (2 min 38.18 sec)

+--------------------------+
| progress                 |
+--------------------------+
| Inserted: 999000/1000000 |
+--------------------------+
1 row in set (2 min 38.39 sec)

+---------------------------+
| progress                  |
+---------------------------+
| Inserted: 1000000/1000000 |
+---------------------------+
1 row in set (2 min 38.48 sec)

Query OK, 0 rows affected (2 min 38.48 sec)

mysql> select * from sales_order limit 10;
+----+---------------+-------------+------------+----------+--------+--------------+------------+------------+
| id | order_no      | customer_id | product_id | quantity | price  | total_amount | order_date | status     |
+----+---------------+-------------+------------+----------+--------+--------------+------------+------------+
|  1 | ORD0000000000 |         916 |         55 |        9 | 685.24 |      6167.16 | 2020-01-07 | cancelled  |
|  2 | ORD0000000001 |         845 |        155 |        1 | 143.77 |       143.77 | 2023-06-30 | completed  |
|  3 | ORD0000000002 |          12 |        351 |        5 | 258.78 |      1293.90 | 2024-08-15 | shipped    |
|  4 | ORD0000000003 |         800 |        301 |        7 | 219.38 |      1535.66 | 2021-05-14 | completed  |
|  5 | ORD0000000004 |         321 |        382 |        9 | 959.75 |      8637.75 | 2021-05-12 | processing |
|  6 | ORD0000000005 |          84 |        160 |        4 | 787.42 |      3149.68 | 2024-10-24 | pending    |
|  7 | ORD0000000006 |         565 |        351 |        9 | 957.87 |      8620.83 | 2021-12-02 | cancelled  |
|  8 | ORD0000000007 |         355 |         65 |        6 | 509.51 |      3057.06 | 2024-04-27 | shipped    |
|  9 | ORD0000000008 |         795 |        338 |       10 | 941.79 |      9417.90 | 2023-12-22 | cancelled  |
| 10 | ORD0000000009 |         844 |        403 |        5 |  55.32 |       276.60 | 2024-02-16 | completed  |
+----+---------------+-------------+------------+----------+--------+--------------+------------+------------+
10 rows in set (0.00 sec)

mysql> select count(*) from sales_order;
+----------+
| count(*) |
+----------+
|  1000000 |
+----------+
1 row in set (0.29 sec)


[mysql@cjc-db-02 cjc]$ ls -lrth
total 185M
-rw-r----- 1 mysql mysql 112K May 31 10:36 t1.ibd
-rw-r----- 1 mysql mysql 184M May 31 10:56 sales_order.ibd

停库

mysql> shutdown;
Query OK, 0 rows affected (0.28 sec)

数据文件拷贝到临时目录

[mysql@cjc-db-02 3308]$ cp /mysqldata/3308/data/cjc/*.ibd /mysqldata/3308/ibd2sql/
[mysql@cjc-db-02 3308]$ ls -lrth /mysqldata/3308/ibd2sql/
total 185M
-rw-r----- 1 mysql mysql 184M May 31 11:05 sales_order.ibd
-rw-r----- 1 mysql mysql 112K May 31 11:05 t1.ibd

ibd2sql解析:

场景一:解析完整的ibd文件
场景二:解析完整的ibd文件,合并同一页面的语句
场景三:解析完整的ibd文件,更换表名
场景四:ibd文件损坏,解析ibd文件部分内容
场景五:恢复误删除的数据
场景六:web控制台查看数据结构

场景一:解析完整的ibd文件

cd /soft/ibd2sql/ibd2sql-1.10
python3 main.py /mysqldata/3308/ibd2sql/t1.ibd --sql --ddl

小表:

[root@cjc-db-02 ibd2sql-1.10]# python3 main.py /mysqldata/3308/ibd2sql/t1.ibd --sql --ddl
CREATE TABLE IF NOT EXISTS `cjc`.`t1`(
    `id` int NOT NULL,
    `name` varchar(10) NULL,
    `time` time NULL,
    PRIMARY KEY  (`id` )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ;
INSERT INTO `cjc`.`t1` VALUES (1, 'x', '10:35:52');
INSERT INTO `cjc`.`t1` VALUES (2, 'y', '10:35:56');
INSERT INTO `cjc`.`t1` VALUES (3, 'z', '10:36:2');
INSERT INTO `cjc`.`t1` VALUES (4, 'u', '10:36:7');
INSERT INTO `cjc`.`t1` VALUES (5, 'v', '10:36:16');

输出到文件

[root@cjc-db-02 ibd2sql-1.10]# python3 main.py /mysqldata/3308/ibd2sql/t1.ibd --sql --ddl  > /mysqldata/3308/ibd2sql/t1.sql

大表:

[mysql@cjc-db-02 ibd2sql-1.5]$ python3 main.py /mysqldata/3308/ibd2sql/sales_order.ibd --sql --ddl > /mysqldata/3308/ibd2sql/sales_order.sql

看看执行时间,需要约8分钟,解析107MB的数据。

[root@cjc-db-02 ibd2sql-1.10]# date && python3 main.py /mysqldata/3308/ibd2sql/sales_order.ibd --sql --ddl > /mysqldata/3308/ibd2sql/sales_order.sql && date
Sat May 31 21:16:49 CST 2025
Sat May 31 21:24:31 CST 2025
[mysql@cjc-db-02 ibd2sql]$ ls -lrth
total 291M
-rw-r----- 1 mysql mysql 184M May 31 11:05 sales_order.ibd
-rw-r----- 1 mysql mysql 112K May 31 11:05 t1.ibd
-rw-r--r-- 1 root  root  107M May 31 21:24 sales_order.sql
-rw-r--r-- 1 root  root   465 May 31 21:25 t1.sql

[mysql@cjc-db-02 ibd2sql]$ cat sales_order.sql |wc -l
1000015
[mysql@cjc-db-02 ibd2sql]$ more sales_order.sql
CREATE TABLE IF NOT EXISTS `cjc`.`sales_order`(
    `id` bigint NOT NULL AUTO_INCREMENT,
    `order_no` varchar(50) NOT NULL,
    `customer_id` int NOT NULL,
    `product_id` int NOT NULL,
    `quantity` int NOT NULL,
    `price` decimal(10,2) NOT NULL,
    `total_amount` decimal(10,2) NULL,
    `order_date` date NOT NULL,
    `status` enum('pending','processing','shipped','completed','cancelled') NOT NULL,
    PRIMARY KEY  (`id` ),
    UNIQUE KEY `order_no` (`order_no` ),
    KEY `idx_customer` (`customer_id` ),
    KEY `idx_date` (`order_date` )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ;
INSERT INTO `cjc`.`sales_order` VALUES (1, 'ORD0000000000', 916, 55, 9, 685.24, '2020-1-7', 'cancelled');
INSERT INTO `cjc`.`sales_order` VALUES (2, 'ORD0000000001', 845, 155, 1, 143.77, '2023-6-30', 'completed');
INSERT INTO `cjc`.`sales_order` VALUES (3, 'ORD0000000002', 12, 351, 5, 258.78, '2024-8-15', 'shipped');
......
INSERT INTO `cjc`.`sales_order` VALUES (1022975, 'ORD0000999997', 429, 286, 6, 147.59, '2025-5-21', 'shipped');
INSERT INTO `cjc`.`sales_order` VALUES (1022976, 'ORD0000999998', 554, 133, 7, 534.72, '2023-8-2', 'completed');
INSERT INTO `cjc`.`sales_order` VALUES (1022977, 'ORD0000999999', 457, 115, 8, 195.82, '2023-5-13', 'shipped');

场景二:解析完整的ibd文件,合并同一页面的语句

[root@cjc-db-02 ibd2sql-1.10]# python3 main.py /mysqldata/3308/ibd2sql/t1.ibd --sql --ddl --complete-insert --table sales_order --multi-value > /mysqldata/3308/ibd2sql/t1_01.sql

[mysql@cjc-db-02 ibd2sql]$ ls -lrth t1*.sql
-rw-r--r-- 1 root  root  465 May 31 21:25 t1.sql
-rw-rw-r-- 1 mysql mysql 379 May 31 21:28 t1_01.sql

将5条insert语句合并为一条

[mysql@cjc-db-02 ibd2sql]$ cat t1_01.sql 
CREATE TABLE IF NOT EXISTS `cjc`.`sales_order`(
    `id` int NOT NULL,
    `name` varchar(10) NULL,
    `time` time NULL,
    PRIMARY KEY  (`id` )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ;
INSERT INTO `cjc`.`sales_order`(`id`,`name`,`time`) VALUES (1, 'x', '10:35:52'),(2, 'y', '10:35:56'),(3, 'z', '10:36:2'),(4, 'u', '10:36:7'),(5, 'v', '10:36:16');

类似于mysqldump的 --extended-insert 参数,可以提高恢复速度。

-e, --extended-insert 
Use multiple-row INSERT syntax that include severalVALUES lists.
[root@cjc-db-02 ibd2sql-1.10]# date && python3 main.py /mysqldata/3308/ibd2sql/sales_order.ibd --sql --ddl --complete-insert --table sales_order --multi-value > /mysqldata/3308/ibd2sql/sales_order_01.sql && date
Sat May 31 21:30:42 CST 2025
Sat May 31 21:37:55 CST 2025

文件大小从107MB缩小到70MB。

[mysql@cjc-db-02 ibd2sql]$ ls -lrth sales_order*.sql
-rw-r--r-- 1 root root 107M May 31 21:24 sales_order.sql
-rw-r--r-- 1 root root  70M May 31 21:37 sales_order_01.sql

行数从1000015缩小到4460

[mysql@cjc-db-02 ibd2sql]$ cat sales_order.sql |wc -l
1000015
[mysql@cjc-db-02 ibd2sql]$ cat sales_order_01.sql |wc -l
4460

场景三:解析完整的ibd文件,更换表名

[root@cjc-db-02 ibd2sql-1.10]# python3 main.py /mysqldata/3308/ibd2sql/t1.ibd --sql --ddl --complete-insert --table t1_change > /mysqldata/3308/ibd2sql/t1_change.sql
[root@cjc-db-02 ibd2sql-1.10]# cat /mysqldata/3308/ibd2sql/t1_change.sql
CREATE TABLE IF NOT EXISTS `cjc`.`t1_change`(
    `id` int NOT NULL,
    `name` varchar(10) NULL,
    `time` time NULL,
    PRIMARY KEY  (`id` )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ;
INSERT INTO `cjc`.`t1_change`(`id`,`name`,`time`) VALUES (1, 'x', '10:35:52');
INSERT INTO `cjc`.`t1_change`(`id`,`name`,`time`) VALUES (2, 'y', '10:35:56');
INSERT INTO `cjc`.`t1_change`(`id`,`name`,`time`) VALUES (3, 'z', '10:36:2');
INSERT INTO `cjc`.`t1_change`(`id`,`name`,`time`) VALUES (4, 'u', '10:36:7');
INSERT INTO `cjc`.`t1_change`(`id`,`name`,`time`) VALUES (5, 'v', '10:36:16');

场景四:ibd文件损坏,解析ibd文件部分内容

破坏文件,模拟数据文件损坏

[root@cjc-db-02 ibd2sql]# cp -a sales_order.ibd sales_order_01.ibd 

破坏最后100MB的数据

dd if=/dev/urandom of=/mysqldata/3308/ibd2sql/sales_order_01.ibd \
      bs=1M \
      seek=$(($(stat -c %s /mysqldata/3308/ibd2sql/sales_order_01.ibd )/1048576 - 100)) \
      count=100 \
      conv=notrunc

前台日志:

100+0 records in
100+0 records out
104857600 bytes (105 MB) copied, 8.77186 s, 12.0 MB/s

参数说明:
conv=notrunc 不截断文件,仅覆盖指定部分

开始解析

[root@cjc-db-02 ibd2sql-1.10]# python3 main.py /mysqldata/3308/ibd2sql/sales_order_01.ibd --sql --ddl > /mysqldata/3308/ibd2sql/sales_order_02.sql

前台报错:

Traceback (most recent call last):
  File "main.py", line 224, in <module>
    ddcw.get_sql()
  File "/soft/ibd2sql/ibd2sql-1.10/ibd2sql/ibd2sql.py", line 263, in get_sql
    for x in _tdata:
  File "/soft/ibd2sql/ibd2sql-1.10/ibd2sql/innodb_page_index.py", line 380, in read_row
    self._read_all_row()
  File "/soft/ibd2sql/ibd2sql-1.10/ibd2sql/innodb_page_index.py", line 505, in _read_all_row
    if rheader.instant_flag and _t_COLUMN_COUNT > _COLUMN_COUNT:
UnboundLocalError: local variable '_COLUMN_COUNT' referenced before assignment

数据量不完整

[mysql@cjc-db-02 ibd2sql]$ cat sales_order_02.sql |wc -l
511102
[mysql@cjc-db-02 ibd2sql]$ ls -lrth sales_order*.sql
-rw-r--r-- 1 root  root  107M May 31 21:24 sales_order.sql
-rw-r--r-- 1 root  root   70M May 31 21:37 sales_order_01.sql
-rw-rw-r-- 1 mysql mysql  55M May 31 21:51 sales_order_02.sql

[mysql@cjc-db-02 ibd2sql]$ tail -n 10 sales_order_02.sql 
INSERT INTO `cjc`.`sales_order` VALUES (522831, 'ORD0000511077', 138, 346, 1, 141.50, '2022-12-17', 'processing');
INSERT INTO `cjc`.`sales_order` VALUES (522832, 'ORD0000511078', 903, 300, 3, 641.24, '2021-10-16', 'completed');
INSERT INTO `cjc`.`sales_order` VALUES (522833, 'ORD0000511079', 634, 500, 1, 481.16, '2020-7-10', 'pending');
INSERT INTO `cjc`.`sales_order` VALUES (522834, 'ORD0000511080', 966, 338, 5, 395.44, '2022-9-19', 'processing');
INSERT INTO `cjc`.`sales_order` VALUES (522835, 'ORD0000511081', 79, 226, 1, 760.00, '2023-12-12', 'processing');
INSERT INTO `cjc`.`sales_order` VALUES (522836, 'ORD0000511082', 491, 231, 9, 776.41, '2022-1-24', 'shipped');
INSERT INTO `cjc`.`sales_order` VALUES (522837, 'ORD0000511083', 693, 383, 8, 468.53, '2020-4-29', 'cancelled');
INSERT INTO `cjc`.`sales_order` VALUES (522838, 'ORD0000511084', 368, 55, 5, 877.08, '2020-4-27', 'completed');
INSERT INTO `cjc`.`sales_order` VALUES (522839, 'ORD0000511085', 152, 380, 4, 450.51, '2021-1-15', 'completed');
INSERT INTO `cjc`.`sales_order` VALUES (522840, 'ORD0000511086', 516, 363, 1, 211.71, '2024-5-4', 'processing');

遇到错误页面强制解析

[root@cjc-db-02 ibd2sql-1.10]# python3 main.py /mysqldata/3308/ibd2sql/sales_order_01.ibd --sql --ddl --force > /mysqldata/3308/ibd2sql/sales_order_02X.sql
Traceback (most recent call last):
  File "main.py", line 224, in <module>
    ddcw.get_sql()
  File "/soft/ibd2sql/ibd2sql-1.10/ibd2sql/ibd2sql.py", line 263, in get_sql
    for x in _tdata:
  File "/soft/ibd2sql/ibd2sql-1.10/ibd2sql/innodb_page_index.py", line 380, in read_row
    self._read_all_row()
  File "/soft/ibd2sql/ibd2sql-1.10/ibd2sql/innodb_page_index.py", line 505, in _read_all_row
    if rheader.instant_flag and _t_COLUMN_COUNT > _COLUMN_COUNT:
UnboundLocalError: local variable '_COLUMN_COUNT' referenced before assignment

[mysql@cjc-db-02 ibd2sql]$ cat sales_order_02X.sql |wc -l
511102

因为破坏的是末尾数据页的数据,如果破坏的是中间的数据页的数据,不清楚 --force 参数是否可以跳过中间丢失的数据,继续恢复后面的数据?

场景五:恢复误删除的数据

在 MySQL 的 InnoDB 存储引擎中,执行 DELETE FROM t1 删除数据时,实际上只是进行标记删除,在行记录的头部设置 deleted_flag=1,而非立即物理删除。
在数据被覆盖之前,理论上还没有恢复出来。
启动数据库

[mysql@cjc-db-02 ~]$ mysqld --defaults-file=/etc/my.cnf --user=mysql &

mysql> select * from cjc.t1;
+----+------+----------+
| id | name | time     |
+----+------+----------+
|  1 | x    | 10:35:52 |
|  2 | y    | 10:35:56 |
|  3 | z    | 10:36:02 |
|  4 | u    | 10:36:07 |
|  5 | v    | 10:36:16 |
+----+------+----------+
5 rows in set (0.57 sec)

模拟误删除

mysql> delete from cjc.t1 where id>3;
Query OK, 2 rows affected (0.36 sec)

mysql> select * from cjc.t1;
+----+------+----------+
| id | name | time     |
+----+------+----------+
|  1 | x    | 10:35:52 |
|  2 | y    | 10:35:56 |
|  3 | z    | 10:36:02 |
+----+------+----------+
3 rows in set (0.00 sec)
[root@cjc-db-02 ibd2sql-1.10]# cp -a /mysqldata/3308/data/cjc/t1.ibd /mysqldata/3308/ibd2sql/t1_XXX.ibd

仅打印被标记为已删除的数据

[root@cjc-db-02 ibd2sql-1.10]# python3 main.py /mysqldata/3308/ibd2sql/t1_XXX.ibd --sql --ddl --delete
CREATE TABLE IF NOT EXISTS `cjc`.`t1`(
    `id` int NOT NULL,
    `name` varchar(10) NULL,
    `time` time NULL,
    PRIMARY KEY  (`id` )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ;
INSERT INTO `cjc`.`t1` VALUES (5, 'v', '10:36:16');
INSERT INTO `cjc`.`t1` VALUES (4, 'u', '10:36:7');

使用完整的插入语句

[root@cjc-db-02 ibd2sql-1.10]# python3 main.py /mysqldata/3308/ibd2sql/t1_XXX.ibd --sql --ddl --delete --complete-insert
CREATE TABLE IF NOT EXISTS `cjc`.`t1`(
    `id` int NOT NULL,
    `name` varchar(10) NULL,
    `time` time NULL,
    PRIMARY KEY  (`id` )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ;
INSERT INTO `cjc`.`t1`(`id`,`name`,`time`) VALUES (5, 'v', '10:36:16');
INSERT INTO `cjc`.`t1`(`id`,`name`,`time`) VALUES (4, 'u', '10:36:7');

场景六:web控制台查看数据结构

支持web控制台查看数据结构

[root@cjc-db-02 ibd2sql-1.10]# python3 ibd2sql_web.py /mysqldata/3308/ibd2sql/t1.ibd 

###############################
#     ibd2sql web console     #
###############################
http://0.0.0.0:8080
改成实际IP

图片.png
图片.png

[root@cjc-db-02 ibd2sql-1.10]# python3 ibd2sql_web.py /mysqldata/3308/ibd2sql/sales_order.ibd 

图片.png

图片.png
图片.png
图片.png
图片.png

下载ibd2sql

关注《IT小Chen》公众号,私信关键字“ibd2sql”,可获取下载链接!

参考:

https://github.com/ddcw/ibd2sql

###chenjuchao 20250601###
欢迎关注我的公众号《IT小Chen

简介:

ibd2sql 是一个使用纯python3编写的离线解析MYSQL INNODB存储引擎的ibd文件的工具,并将其转换为 SQL 语句。
无第三方依赖包. 使用GPL-3.0 license.

特点:

1.方便: 提取表DDL
2.实用: 可替换库(--schema)/表(--table)名, 可在sql语句中输出完整的字段(--complete)
3.简单: 纯python3代码编写, 无依赖包.
4.支持众多数据类型: 支持所有mysql数据类型
5.支持复杂的表结构: 分区表, 注释, 主键, 外键, 约束, 自增, 普通索引, 前缀索引, 主键前缀索引, 唯一索引, 复合索引, 默认值, 符号, 虚拟字段, INSTANT, 无主键等情况的表
6.数据误删恢复: 可以输出被标记为deleted的数据
7.安全: 离线解析ibd文件, 仅可读权限即可
8.支持范围广: 支持mysql 5.6 or 5.7 or 8.0 or 8.4 or 9.0

版本更新:

[root@cjc-db-02 ibd2sql-1.10]# cat README_zh.md 
版本   更新时间   说明
v0.1  2023.04.27  第一个版本
v0.2  2023.08.30  支持更多数据类型
v0.3  2023.10.13  支持5.7升级到8.0的ibd文件
v1.0  2024.01.05  支持debug,支持更多类型和功能
v1.1  2024.04.12  修复一些bug
v1.2  2024.04.25  新增空间坐标的支持
v1.3  2024.05.11  支持mysql 5.6, 5.7
v1.4  2024.05.21  支持溢出页, 子分区
v1.5  2024.07.10  vector and instant BUG
v1.6  2024.09.19  修复一些bug
v1.7  2024.10.29  1.修复一些bug 2.支持压缩页 3.支持drop table的恢复 4.ucs2,utf16,utf32 字符集支持             |
v1.8  2024.11.09  1.支持所有字符集 2.支持kering 插件加密的表 3.支持web控制台查看数据结构 4.支持修改lower_case_table_names|
v1.9  2025.02.21  修复已知BUG, 支持直接解析5.7的ibd文件
v1.10 2025.04.16  修复已知BUG, 添加快速统计表行数的脚本

需求与支持:

require: python3
support range: mysql5.x 8.x 9.x

下载和安装:

下载和安装:ibd2sql-1.10.tar.gz

v1.10 下载地址:离线上传到服务器

https://github.com/ddcw/ibd2sql/archive/refs/tags/v1.10.tar.gz

或者如果服务器可以联网,可以直接wget下载到服务器:

wget https://github.com/ddcw/ibd2sql/archive/refs/tags/v1.10.tar.gz

查看 ibd2sql-1.10.tar.gz:

[mysql@cjc-db-02 ibd2sql]$ ls -lrth ibd2sql-1.10.tar.gz 
-rw-r--r-- 1 root root 113K May 31 20:07 ibd2sql-1.10.tar.gz

[mysql@cjc-db-02 ibd2sql]$ md5sum ibd2sql-1.10.tar.gz 
3e73b0bf7ab7b7f65d3ddac413a44163  ibd2sql-1.10.tar.gz

解压:

[root@cjc-db-02 ibd2sql]# tar -zxvf ibd2sql-1.10.tar.gz 

[root@cjc-db-02 ibd2sql-1.10]# ls -lrth
total 164K
-rw-rw-r-- 1 root root  31K Apr 16 18:42 xfs_recovery_v0.3.py
-rw-rw-r-- 1 root root  27K Apr 16 18:42 test.sh
-rw-rw-r-- 1 root root 2.2K Apr 16 18:42 super_fast_count.py
-rw-rw-r-- 1 root root 3.5K Apr 16 18:42 README_zh.md
-rw-rw-r-- 1 root root 3.5K Apr 16 18:42 README.md
-rw-rw-r-- 1 root root 2.6K Apr 16 18:42 modify_lower_case_table_names.py
-rw-rw-r-- 1 root root 8.9K Apr 16 18:42 main.py
-rw-rw-r-- 1 root root  35K Apr 16 18:42 LICENSE
-rw-rw-r-- 1 root root  21K Apr 16 18:42 ibd2sql_web.py
-rw-rw-r-- 1 root root  12K Apr 16 18:42 ibd2sql_mini_for_redundant.py
drwxrwxr-x 2 root root   99 Apr 16 18:42 docs
drwxrwxr-x 3 root root 4.0K May 31 20:35 ibd2sql

下载和安装 Python:

下载地址:

https://www.python.org/downloads/release/python-361/

图片.png
图片.png

上传:

[root@cjc-db-02 ibd2sql]# ls -lrth Python-3.6.1.tgz 
-rw-r--r-- 1 root root 22M May 31 08:44 Python-3.6.1.tgz

[root@cjc-db-02 ibd2sql]# md5sum Python-3.6.1.tgz 
2d0fc9f3a5940707590e07f03ecb08b9  Python-3.6.1.tgz

解压:

tar xvf Python-3.6.1.tgz
cd Python-3.6.1
mkdir -p /usr/local/python3
./configure --prefix=/usr/local/python3
make && make install
ln -s /usr/local/python3/bin/python3 /usr/bin/python3

[root@cjc-db-02 Python-3.6.1]# python3  -V
Python 3.6.1

下载和安装:MySQL 8.4.5

https://dev.mysql.com/downloads/mysql/

图片.png
图片.png

[root@cjc-db-02 mysql]# ls -lrth
total 2.7G
-rw-r--r-- 1 7155 31415 868M Apr  1 18:11 mysql-8.4.5-linux-glibc2.17-x86_64.tar.xz
-rw-r--r-- 1 7155 31415  78M Apr  1 18:25 mysql-router-8.4.5-linux-glibc2.17-x86_64.tar.xz
-rw-r--r-- 1 7155 31415 432M Apr  1 18:44 mysql-test-8.4.5-linux-glibc2.17-x86_64.tar.xz
-rw-r--r-- 1 root root  1.4G May 31 09:51 mysql-8.4.5-linux-glibc2.17-x86_64.tar
[root@cjc-db-02 mysql]# md5sum mysql-*.tar.xz
8c7573c055a38821f8e65946f48a07ae  mysql-8.4.5-linux-glibc2.17-x86_64.tar.xz
55c76f9b10328e821fac1dbc80204a5e  mysql-router-8.4.5-linux-glibc2.17-x86_64.tar.xz

安装过程省略:
和 8.0 相比,已经删除了 relay_log_info_repository 参数,如果my.cnf配置了,需要参数这个参数。

ibd2sql工具测试:

1.ibd2sql常用命令

帮助信息:

[mysql@cjc-db-02 ibd2sql-1.5]$ python3 main.py --help
[root@cjc-db-02 ibd2sql-1.10]# python3 main.py --help
usage: main.py [--help] [--version] [--ddl] [--sql] [--delete]
               [--complete-insert] [--force] [--set] [--multi-value]
               [--replace] [--table TABLE_NAME] [--schema SCHEMA_NAME]
               [--sdi-table SDI_TABLE] [--where-trx WHERE_TRX]
               [--where-rollptr WHERE_ROLLPTR] [--limit LIMIT] [--debug]
               [--debug-file DEBUG_FILE] [--page-min PAGE_MIN]
               [--page-max PAGE_MAX] [--page-start PAGE_START]
               [--page-count PAGE_COUNT] [--page-skip PAGE_SKIP] [--mysql5]
               [--keyring-file KEYRING_FILE]
               [FILENAME]

解析mysql 5.7/8.0的ibd文件 https://github.com/ddcw/ibd2sql

positional arguments:
  FILENAME              ibd filename

optional arguments:
  --help, -h            show help
  --version, -v, -V     show version
  --ddl, -d             print ddl
  --sql                 print data by sql
  --delete              print data only for flag of deleted
  --complete-insert     use complete insert statements for sql
  --force, -f           force pasrser file when Error Page
  --set                 set/enum to fill in actual data instead of strings
  --multi-value         single sql if data belong to one page
  --replace             "REPLACE INTO" replace to "INSERT INTO" (default)
  --table TABLE_NAME    replace table name except ddl
  --schema SCHEMA_NAME  replace table name except ddl
  --sdi-table SDI_TABLE
                        read SDI PAGE from this file(ibd)(partition table)
  --where-trx WHERE_TRX
                        default (0,281474976710656)
  --where-rollptr WHERE_ROLLPTR
                        default (0,72057594037927936)
  --limit LIMIT         limit rows
  --debug, -D           will DEBUG (it's too big)
  --debug-file DEBUG_FILE
                        default sys.stdout if DEBUG
  --page-min PAGE_MIN   if PAGE NO less than it, will break
  --page-max PAGE_MAX   if PAGE NO great than it, will break
  --page-start PAGE_START
                        INDEX PAGE START NO
  --page-count PAGE_COUNT
                        page count NO
  --page-skip PAGE_SKIP
                        skip some pages when start parse index page
  --mysql5              for mysql5.7 flag
  --keyring-file KEYRING_FILE, -k KEYRING_FILE
                        keyring filename
Example:
ibd2sql /data/db1/xxx.ibd --ddl --sql
ibd2sql /data/db1/xxx.ibd --delete --sql
ibd2sql /data/db1/xxx#p#p1.ibd --sdi-table /data/db1/xxx#p#p0.ibd --sql
ibd2sql /mysql57/db1/xxx.ibd --sdi-table /mysql80/db1/xxx.ibd --sql --mysql5

常用命令:

--version, -v, -V: 显示版本信息。
--ddl, -d: 打印 DDL。
--sql: 打印数据的 SQL 语句。
--delete: 仅打印被标记为已删除的数据。
--complete-insert: 使用完整的插入语句。
--force, -f: 在遇到错误页面时强制解析文件。
--set: 用实际数据填充 set/enum 而不是字符串。
--multi-value: 如果数据属于同一页面,则使用单个 SQL 语句。
--replace: 使用 "REPLACE INTO" 替换 "INSERT INTO"(默认)。
--table TABLE_NAME: 替换表名(不包括 DDL)。
--schema SCHEMA_NAME: 替换库名(不包括 DDL)。
--sdi-table SDI_TABLE: 从这个 IBD 文件(分区表)读取 SDI 页面。
--where-trx WHERE_TRX: 事务过滤范围,默认 (0,281474976710656)。
--where-rollptr WHERE_ROLLPTR: 回滚指针过滤范围,默认 (0,72057594037927936)。
--limit LIMIT: 限制行数。
--debug, -D: 调试模式(输出非常大)。
--debug-file DEBUG_FILE: 调试输出文件,默认为 sys.stdout。
--page-min PAGE_MIN: 如果页面编号小于此值,则终止。
--page-max PAGE_MAX: 如果页面编号大于此值,则终止。
--page-start PAGE_START: 索引页面起始编号。
--page-count PAGE_COUNT: 页面计数编号。
--page-skip PAGE_SKIP: 解析索引页面时跳过某些页面。
--mysql5: 适用于 MySQL 5.7 标志。

2.准备测试数据

创建一张小表cjc.t1和一张大表cjc.sales_order;

mysql> create database cjc;
mysql> use cjc;
create table t1(id int primary key,name varchar(10),time time);
insert into t1 values(1,'x',now());
insert into t1 values(2,'y',now());
insert into t1 values(3,'z',now());
insert into t1 values(4,'u',now());
insert into t1 values(5,'v',now());

mysql> select * from cjc.t1;
+----+------+----------+
| id | name | time     |
+----+------+----------+
|  1 | x    | 10:35:52 |
|  2 | y    | 10:35:56 |
|  3 | z    | 10:36:02 |
|  4 | u    | 10:36:07 |
|  5 | v    | 10:36:16 |
+----+------+----------+
5 rows in set (0.00 sec)


CREATE TABLE sales_order (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    order_no VARCHAR(50) NOT NULL UNIQUE,
    customer_id INT NOT NULL,
    product_id INT NOT NULL,
    quantity INT NOT NULL,
    price DECIMAL(10,2) NOT NULL,
    total_amount DECIMAL(10,2) GENERATED ALWAYS AS (quantity * price) STORED,
    order_date DATE NOT NULL,
    status ENUM('pending', 'processing', 'shipped', 'completed', 'cancelled') NOT NULL,
    INDEX idx_customer (customer_id),
    INDEX idx_date (order_date)
) ENGINE=InnoDB;

创建存储过程:

DELIMITER $$

CREATE PROCEDURE GenerateSalesOrders()
BEGIN
    DECLARE counter INT DEFAULT 0;
    DECLARE batchSize INT DEFAULT 1000;  -- 每批插入1000条
    DECLARE maxRecords INT DEFAULT 1000000;
    
    WHILE counter < maxRecords DO
        INSERT INTO sales_order (
            order_no, 
            customer_id, 
            product_id, 
            quantity, 
            price, 
            order_date, 
            status
        )
        SELECT
            CONCAT('ORD', LPAD(counter + n, 10, '0')),  -- 唯一订单号
            FLOOR(1 + RAND() * 1000),                   -- 客户ID 1-1000
            FLOOR(1 + RAND() * 500),                   -- 产品ID 1-500
            FLOOR(1 + RAND() * 10),                    -- 数量 1-10
            ROUND(10 + RAND() * 990, 2),               -- 价格 10.00-999.99
            DATE_ADD('2020-01-01', INTERVAL FLOOR(RAND() * 2000) DAY),  -- 日期 2020-2025
            ELT(FLOOR(1 + RAND() * 5), 'pending', 'processing', 'shipped', 'completed', 'cancelled') -- 状态
        FROM (
            SELECT a.N + b.N * 10 + c.N * 100 AS n
            FROM 
                (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) a,
                (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) b,
                (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) c
            ORDER BY n
            LIMIT batchSize
        ) numbers;
        
        SET counter = counter + batchSize;
        COMMIT;
        SELECT CONCAT('Inserted: ', counter, '/1000000') AS progress;
    END WHILE;
END$$

DELIMITER ;

调用存储过程(耗时约5-15分钟)

CALL GenerateSalesOrders();
......
+--------------------------+
| progress                 |
+--------------------------+
| Inserted: 998000/1000000 |
+--------------------------+
1 row in set (2 min 38.18 sec)

+--------------------------+
| progress                 |
+--------------------------+
| Inserted: 999000/1000000 |
+--------------------------+
1 row in set (2 min 38.39 sec)

+---------------------------+
| progress                  |
+---------------------------+
| Inserted: 1000000/1000000 |
+---------------------------+
1 row in set (2 min 38.48 sec)

Query OK, 0 rows affected (2 min 38.48 sec)

mysql> select * from sales_order limit 10;
+----+---------------+-------------+------------+----------+--------+--------------+------------+------------+
| id | order_no      | customer_id | product_id | quantity | price  | total_amount | order_date | status     |
+----+---------------+-------------+------------+----------+--------+--------------+------------+------------+
|  1 | ORD0000000000 |         916 |         55 |        9 | 685.24 |      6167.16 | 2020-01-07 | cancelled  |
|  2 | ORD0000000001 |         845 |        155 |        1 | 143.77 |       143.77 | 2023-06-30 | completed  |
|  3 | ORD0000000002 |          12 |        351 |        5 | 258.78 |      1293.90 | 2024-08-15 | shipped    |
|  4 | ORD0000000003 |         800 |        301 |        7 | 219.38 |      1535.66 | 2021-05-14 | completed  |
|  5 | ORD0000000004 |         321 |        382 |        9 | 959.75 |      8637.75 | 2021-05-12 | processing |
|  6 | ORD0000000005 |          84 |        160 |        4 | 787.42 |      3149.68 | 2024-10-24 | pending    |
|  7 | ORD0000000006 |         565 |        351 |        9 | 957.87 |      8620.83 | 2021-12-02 | cancelled  |
|  8 | ORD0000000007 |         355 |         65 |        6 | 509.51 |      3057.06 | 2024-04-27 | shipped    |
|  9 | ORD0000000008 |         795 |        338 |       10 | 941.79 |      9417.90 | 2023-12-22 | cancelled  |
| 10 | ORD0000000009 |         844 |        403 |        5 |  55.32 |       276.60 | 2024-02-16 | completed  |
+----+---------------+-------------+------------+----------+--------+--------------+------------+------------+
10 rows in set (0.00 sec)

mysql> select count(*) from sales_order;
+----------+
| count(*) |
+----------+
|  1000000 |
+----------+
1 row in set (0.29 sec)


[mysql@cjc-db-02 cjc]$ ls -lrth
total 185M
-rw-r----- 1 mysql mysql 112K May 31 10:36 t1.ibd
-rw-r----- 1 mysql mysql 184M May 31 10:56 sales_order.ibd

停库

mysql> shutdown;
Query OK, 0 rows affected (0.28 sec)

数据文件拷贝到临时目录

[mysql@cjc-db-02 3308]$ cp /mysqldata/3308/data/cjc/*.ibd /mysqldata/3308/ibd2sql/
[mysql@cjc-db-02 3308]$ ls -lrth /mysqldata/3308/ibd2sql/
total 185M
-rw-r----- 1 mysql mysql 184M May 31 11:05 sales_order.ibd
-rw-r----- 1 mysql mysql 112K May 31 11:05 t1.ibd

ibd2sql解析:

场景一:解析完整的ibd文件
场景二:解析完整的ibd文件,合并同一页面的语句
场景三:解析完整的ibd文件,更换表名
场景四:ibd文件损坏,解析ibd文件部分内容
场景五:恢复误删除的数据
场景六:web控制台查看数据结构

场景一:解析完整的ibd文件

cd /soft/ibd2sql/ibd2sql-1.10
python3 main.py /mysqldata/3308/ibd2sql/t1.ibd --sql --ddl

小表:

[root@cjc-db-02 ibd2sql-1.10]# python3 main.py /mysqldata/3308/ibd2sql/t1.ibd --sql --ddl
CREATE TABLE IF NOT EXISTS `cjc`.`t1`(
    `id` int NOT NULL,
    `name` varchar(10) NULL,
    `time` time NULL,
    PRIMARY KEY  (`id` )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ;
INSERT INTO `cjc`.`t1` VALUES (1, 'x', '10:35:52');
INSERT INTO `cjc`.`t1` VALUES (2, 'y', '10:35:56');
INSERT INTO `cjc`.`t1` VALUES (3, 'z', '10:36:2');
INSERT INTO `cjc`.`t1` VALUES (4, 'u', '10:36:7');
INSERT INTO `cjc`.`t1` VALUES (5, 'v', '10:36:16');

输出到文件

[root@cjc-db-02 ibd2sql-1.10]# python3 main.py /mysqldata/3308/ibd2sql/t1.ibd --sql --ddl  > /mysqldata/3308/ibd2sql/t1.sql

大表:

[mysql@cjc-db-02 ibd2sql-1.5]$ python3 main.py /mysqldata/3308/ibd2sql/sales_order.ibd --sql --ddl > /mysqldata/3308/ibd2sql/sales_order.sql

看看执行时间,需要约8分钟,解析107MB的数据。

[root@cjc-db-02 ibd2sql-1.10]# date && python3 main.py /mysqldata/3308/ibd2sql/sales_order.ibd --sql --ddl > /mysqldata/3308/ibd2sql/sales_order.sql && date
Sat May 31 21:16:49 CST 2025
Sat May 31 21:24:31 CST 2025
[mysql@cjc-db-02 ibd2sql]$ ls -lrth
total 291M
-rw-r----- 1 mysql mysql 184M May 31 11:05 sales_order.ibd
-rw-r----- 1 mysql mysql 112K May 31 11:05 t1.ibd
-rw-r--r-- 1 root  root  107M May 31 21:24 sales_order.sql
-rw-r--r-- 1 root  root   465 May 31 21:25 t1.sql

[mysql@cjc-db-02 ibd2sql]$ cat sales_order.sql |wc -l
1000015
[mysql@cjc-db-02 ibd2sql]$ more sales_order.sql
CREATE TABLE IF NOT EXISTS `cjc`.`sales_order`(
    `id` bigint NOT NULL AUTO_INCREMENT,
    `order_no` varchar(50) NOT NULL,
    `customer_id` int NOT NULL,
    `product_id` int NOT NULL,
    `quantity` int NOT NULL,
    `price` decimal(10,2) NOT NULL,
    `total_amount` decimal(10,2) NULL,
    `order_date` date NOT NULL,
    `status` enum('pending','processing','shipped','completed','cancelled') NOT NULL,
    PRIMARY KEY  (`id` ),
    UNIQUE KEY `order_no` (`order_no` ),
    KEY `idx_customer` (`customer_id` ),
    KEY `idx_date` (`order_date` )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ;
INSERT INTO `cjc`.`sales_order` VALUES (1, 'ORD0000000000', 916, 55, 9, 685.24, '2020-1-7', 'cancelled');
INSERT INTO `cjc`.`sales_order` VALUES (2, 'ORD0000000001', 845, 155, 1, 143.77, '2023-6-30', 'completed');
INSERT INTO `cjc`.`sales_order` VALUES (3, 'ORD0000000002', 12, 351, 5, 258.78, '2024-8-15', 'shipped');
......
INSERT INTO `cjc`.`sales_order` VALUES (1022975, 'ORD0000999997', 429, 286, 6, 147.59, '2025-5-21', 'shipped');
INSERT INTO `cjc`.`sales_order` VALUES (1022976, 'ORD0000999998', 554, 133, 7, 534.72, '2023-8-2', 'completed');
INSERT INTO `cjc`.`sales_order` VALUES (1022977, 'ORD0000999999', 457, 115, 8, 195.82, '2023-5-13', 'shipped');

场景二:解析完整的ibd文件,合并同一页面的语句

[root@cjc-db-02 ibd2sql-1.10]# python3 main.py /mysqldata/3308/ibd2sql/t1.ibd --sql --ddl --complete-insert --table sales_order --multi-value > /mysqldata/3308/ibd2sql/t1_01.sql

[mysql@cjc-db-02 ibd2sql]$ ls -lrth t1*.sql
-rw-r--r-- 1 root  root  465 May 31 21:25 t1.sql
-rw-rw-r-- 1 mysql mysql 379 May 31 21:28 t1_01.sql

将5条insert语句合并为一条

[mysql@cjc-db-02 ibd2sql]$ cat t1_01.sql 
CREATE TABLE IF NOT EXISTS `cjc`.`sales_order`(
    `id` int NOT NULL,
    `name` varchar(10) NULL,
    `time` time NULL,
    PRIMARY KEY  (`id` )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ;
INSERT INTO `cjc`.`sales_order`(`id`,`name`,`time`) VALUES (1, 'x', '10:35:52'),(2, 'y', '10:35:56'),(3, 'z', '10:36:2'),(4, 'u', '10:36:7'),(5, 'v', '10:36:16');

类似于mysqldump的 --extended-insert 参数,可以提高恢复速度。

-e, --extended-insert 
Use multiple-row INSERT syntax that include severalVALUES lists.
[root@cjc-db-02 ibd2sql-1.10]# date && python3 main.py /mysqldata/3308/ibd2sql/sales_order.ibd --sql --ddl --complete-insert --table sales_order --multi-value > /mysqldata/3308/ibd2sql/sales_order_01.sql && date
Sat May 31 21:30:42 CST 2025
Sat May 31 21:37:55 CST 2025

文件大小从107MB缩小到70MB。

[mysql@cjc-db-02 ibd2sql]$ ls -lrth sales_order*.sql
-rw-r--r-- 1 root root 107M May 31 21:24 sales_order.sql
-rw-r--r-- 1 root root  70M May 31 21:37 sales_order_01.sql

行数从1000015缩小到4460

[mysql@cjc-db-02 ibd2sql]$ cat sales_order.sql |wc -l
1000015
[mysql@cjc-db-02 ibd2sql]$ cat sales_order_01.sql |wc -l
4460

场景三:解析完整的ibd文件,更换表名

[root@cjc-db-02 ibd2sql-1.10]# python3 main.py /mysqldata/3308/ibd2sql/t1.ibd --sql --ddl --complete-insert --table t1_change > /mysqldata/3308/ibd2sql/t1_change.sql
[root@cjc-db-02 ibd2sql-1.10]# cat /mysqldata/3308/ibd2sql/t1_change.sql
CREATE TABLE IF NOT EXISTS `cjc`.`t1_change`(
    `id` int NOT NULL,
    `name` varchar(10) NULL,
    `time` time NULL,
    PRIMARY KEY  (`id` )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ;
INSERT INTO `cjc`.`t1_change`(`id`,`name`,`time`) VALUES (1, 'x', '10:35:52');
INSERT INTO `cjc`.`t1_change`(`id`,`name`,`time`) VALUES (2, 'y', '10:35:56');
INSERT INTO `cjc`.`t1_change`(`id`,`name`,`time`) VALUES (3, 'z', '10:36:2');
INSERT INTO `cjc`.`t1_change`(`id`,`name`,`time`) VALUES (4, 'u', '10:36:7');
INSERT INTO `cjc`.`t1_change`(`id`,`name`,`time`) VALUES (5, 'v', '10:36:16');

场景四:ibd文件损坏,解析ibd文件部分内容

破坏文件,模拟数据文件损坏

[root@cjc-db-02 ibd2sql]# cp -a sales_order.ibd sales_order_01.ibd 

破坏最后100MB的数据

dd if=/dev/urandom of=/mysqldata/3308/ibd2sql/sales_order_01.ibd \
      bs=1M \
      seek=$(($(stat -c %s /mysqldata/3308/ibd2sql/sales_order_01.ibd )/1048576 - 100)) \
      count=100 \
      conv=notrunc

前台日志:

100+0 records in
100+0 records out
104857600 bytes (105 MB) copied, 8.77186 s, 12.0 MB/s

参数说明:
conv=notrunc 不截断文件,仅覆盖指定部分

开始解析

[root@cjc-db-02 ibd2sql-1.10]# python3 main.py /mysqldata/3308/ibd2sql/sales_order_01.ibd --sql --ddl > /mysqldata/3308/ibd2sql/sales_order_02.sql

前台报错:

Traceback (most recent call last):
  File "main.py", line 224, in <module>
    ddcw.get_sql()
  File "/soft/ibd2sql/ibd2sql-1.10/ibd2sql/ibd2sql.py", line 263, in get_sql
    for x in _tdata:
  File "/soft/ibd2sql/ibd2sql-1.10/ibd2sql/innodb_page_index.py", line 380, in read_row
    self._read_all_row()
  File "/soft/ibd2sql/ibd2sql-1.10/ibd2sql/innodb_page_index.py", line 505, in _read_all_row
    if rheader.instant_flag and _t_COLUMN_COUNT > _COLUMN_COUNT:
UnboundLocalError: local variable '_COLUMN_COUNT' referenced before assignment

数据量不完整

[mysql@cjc-db-02 ibd2sql]$ cat sales_order_02.sql |wc -l
511102
[mysql@cjc-db-02 ibd2sql]$ ls -lrth sales_order*.sql
-rw-r--r-- 1 root  root  107M May 31 21:24 sales_order.sql
-rw-r--r-- 1 root  root   70M May 31 21:37 sales_order_01.sql
-rw-rw-r-- 1 mysql mysql  55M May 31 21:51 sales_order_02.sql

[mysql@cjc-db-02 ibd2sql]$ tail -n 10 sales_order_02.sql 
INSERT INTO `cjc`.`sales_order` VALUES (522831, 'ORD0000511077', 138, 346, 1, 141.50, '2022-12-17', 'processing');
INSERT INTO `cjc`.`sales_order` VALUES (522832, 'ORD0000511078', 903, 300, 3, 641.24, '2021-10-16', 'completed');
INSERT INTO `cjc`.`sales_order` VALUES (522833, 'ORD0000511079', 634, 500, 1, 481.16, '2020-7-10', 'pending');
INSERT INTO `cjc`.`sales_order` VALUES (522834, 'ORD0000511080', 966, 338, 5, 395.44, '2022-9-19', 'processing');
INSERT INTO `cjc`.`sales_order` VALUES (522835, 'ORD0000511081', 79, 226, 1, 760.00, '2023-12-12', 'processing');
INSERT INTO `cjc`.`sales_order` VALUES (522836, 'ORD0000511082', 491, 231, 9, 776.41, '2022-1-24', 'shipped');
INSERT INTO `cjc`.`sales_order` VALUES (522837, 'ORD0000511083', 693, 383, 8, 468.53, '2020-4-29', 'cancelled');
INSERT INTO `cjc`.`sales_order` VALUES (522838, 'ORD0000511084', 368, 55, 5, 877.08, '2020-4-27', 'completed');
INSERT INTO `cjc`.`sales_order` VALUES (522839, 'ORD0000511085', 152, 380, 4, 450.51, '2021-1-15', 'completed');
INSERT INTO `cjc`.`sales_order` VALUES (522840, 'ORD0000511086', 516, 363, 1, 211.71, '2024-5-4', 'processing');

遇到错误页面强制解析

[root@cjc-db-02 ibd2sql-1.10]# python3 main.py /mysqldata/3308/ibd2sql/sales_order_01.ibd --sql --ddl --force > /mysqldata/3308/ibd2sql/sales_order_02X.sql
Traceback (most recent call last):
  File "main.py", line 224, in <module>
    ddcw.get_sql()
  File "/soft/ibd2sql/ibd2sql-1.10/ibd2sql/ibd2sql.py", line 263, in get_sql
    for x in _tdata:
  File "/soft/ibd2sql/ibd2sql-1.10/ibd2sql/innodb_page_index.py", line 380, in read_row
    self._read_all_row()
  File "/soft/ibd2sql/ibd2sql-1.10/ibd2sql/innodb_page_index.py", line 505, in _read_all_row
    if rheader.instant_flag and _t_COLUMN_COUNT > _COLUMN_COUNT:
UnboundLocalError: local variable '_COLUMN_COUNT' referenced before assignment

[mysql@cjc-db-02 ibd2sql]$ cat sales_order_02X.sql |wc -l
511102

因为破坏的是末尾数据页的数据,如果破坏的是中间的数据页的数据,不清楚 --force 参数是否可以跳过中间丢失的数据,继续恢复后面的数据?

场景五:恢复误删除的数据

在 MySQL 的 InnoDB 存储引擎中,执行 DELETE FROM t1 删除数据时,实际上只是进行标记删除,在行记录的头部设置 deleted_flag=1,而非立即物理删除。
在数据被覆盖之前,理论上还没有恢复出来。
启动数据库

[mysql@cjc-db-02 ~]$ mysqld --defaults-file=/etc/my.cnf --user=mysql &

mysql> select * from cjc.t1;
+----+------+----------+
| id | name | time     |
+----+------+----------+
|  1 | x    | 10:35:52 |
|  2 | y    | 10:35:56 |
|  3 | z    | 10:36:02 |
|  4 | u    | 10:36:07 |
|  5 | v    | 10:36:16 |
+----+------+----------+
5 rows in set (0.57 sec)

模拟误删除

mysql> delete from cjc.t1 where id>3;
Query OK, 2 rows affected (0.36 sec)

mysql> select * from cjc.t1;
+----+------+----------+
| id | name | time     |
+----+------+----------+
|  1 | x    | 10:35:52 |
|  2 | y    | 10:35:56 |
|  3 | z    | 10:36:02 |
+----+------+----------+
3 rows in set (0.00 sec)
[root@cjc-db-02 ibd2sql-1.10]# cp -a /mysqldata/3308/data/cjc/t1.ibd /mysqldata/3308/ibd2sql/t1_XXX.ibd

仅打印被标记为已删除的数据

[root@cjc-db-02 ibd2sql-1.10]# python3 main.py /mysqldata/3308/ibd2sql/t1_XXX.ibd --sql --ddl --delete
CREATE TABLE IF NOT EXISTS `cjc`.`t1`(
    `id` int NOT NULL,
    `name` varchar(10) NULL,
    `time` time NULL,
    PRIMARY KEY  (`id` )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ;
INSERT INTO `cjc`.`t1` VALUES (5, 'v', '10:36:16');
INSERT INTO `cjc`.`t1` VALUES (4, 'u', '10:36:7');

使用完整的插入语句

[root@cjc-db-02 ibd2sql-1.10]# python3 main.py /mysqldata/3308/ibd2sql/t1_XXX.ibd --sql --ddl --delete --complete-insert
CREATE TABLE IF NOT EXISTS `cjc`.`t1`(
    `id` int NOT NULL,
    `name` varchar(10) NULL,
    `time` time NULL,
    PRIMARY KEY  (`id` )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ;
INSERT INTO `cjc`.`t1`(`id`,`name`,`time`) VALUES (5, 'v', '10:36:16');
INSERT INTO `cjc`.`t1`(`id`,`name`,`time`) VALUES (4, 'u', '10:36:7');

场景六:web控制台查看数据结构

支持web控制台查看数据结构

[root@cjc-db-02 ibd2sql-1.10]# python3 ibd2sql_web.py /mysqldata/3308/ibd2sql/t1.ibd 

###############################
#     ibd2sql web console     #
###############################
http://0.0.0.0:8080
改成实际IP

图片.png
图片.png

[root@cjc-db-02 ibd2sql-1.10]# python3 ibd2sql_web.py /mysqldata/3308/ibd2sql/sales_order.ibd 

图片.png

图片.png
图片.png
图片.png
图片.png

下载ibd2sql

关注《IT小Chen》公众号,私信关键字“ibd2sql”,可获取下载链接!

参考:

https://github.com/ddcw/ibd2sql

###chenjuchao 20250601###
欢迎关注我的公众号《IT小Chen

### 使用 `ibd2sql` 进行 MySQL IBD 文件的数据恢复 #### 准备工作 为了成功使用 `ibd2sql` 工具进行数据恢复,需准备如下环境和文件: - 安装 Python 3.x 及其依赖库。 - 获取目标版本的 MySQL 实例运行权限。 - 备份待恢复的 `.ibd` 文件及其对应的 `.frm` 文件(如果适用)。对于较新版本的 MySQL (8.0+),`.frm` 文件可能不再存在。 #### 执行过程 通过 `ibd2sql` 工具解析并转换 IBD 文件为 SQL 脚本的过程涉及几个关键步骤。下面是一个具体的例子来说明这一流程[^2]。 假设有一个名为 `ddcw_alltype_table` 的表,在不同版本之间迁移时遇到问题,则可以按照以下方式操作: 1. **提取 DDL** 对于 MySQL 5.6/5.7 版本中的表结构定义(.frm),可以通过 `dbsake frmdump` 命令导出创建该表所需的 SQL 语句,并将其导入至更高版本的 MySQL 中作为元数据源: ```bash dbsake frmdump test.frm | mysql -u root -p ``` 2. **调用 ibd2sql 解析 IBD 文件** 接下来利用 `ibd2sql` 将旧版 MySQL 表空间(`.ibd`)转成可读取的 SQL 插入语句。这里指定两个路径参数分别指向新版与旧版的同名表格的空间文件位置: ```bash python3 main.py \ --sdi-table /your_mysql8_path/ddcw_alltype_table.ibd \ /your_mysql5_path/ddcw_alltype_table.ibd \ --sql --mysql5 ``` 上述命令会生成一系列 INSERT INTO ... VALUES (...) 形式的SQL指令,这些指令可以直接应用于新的数据库环境中完成数据加载。 请注意,当面对非常大尺寸或是已经遭受一定程度物理损伤的 IBD 文件时,整个过程中可能会遭遇性能瓶颈或者是无法完全重建原始记录的情况。因此建议提前做好充分测试以及风险评估[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值