简介
本文章主要讲解 MySQL 异常宕机等意外情况下导致 ibd 文件损坏,配置 mysql
强制恢复(innodb_force_recovery
) 1-6 级仍然无法启动的情况。下面是恢复整个实例的所有流程和思路。
这里恢复的实例是 mysql5.6 的整个流程,5.7和 5.6 类似,8.0 可以直接用
ibd2sql
进行坏表的恢复。再次提醒:建议 MySQL 开启定时全备、开启
row
格式binlog
、创建从库等冗余操作,防止重要数据丢失!!!操作前请行备份!!!
操作前请确认磁盘空间!!!
环境:
机器异常宕机后,MySQL 5.6 的实例恢复模式无法启动,没有备份和完整的 binlog,客户要求尽可能的恢复数据。
恢复思路:
-
1、先将所有
.ibd
文件使用释放&导入表空间的方式恢复一遍到新实例 -
2、使用
ibd2sql
将第一步无法恢复的innodb
表导出为SQL
并恢复 -
3、将
MyISAM
表导入到新的实例 -
4、使用导入导出的方式恢复系统表(user、event、func、proc)
-
5、使用
ibd2sql
导出视图并恢复 -
6、mysqlcheck 检查和修复一遍所有表
-
7、逻辑备份新实例&然后在新建个实例恢复进去 (因为有可能也是换的)
注意:
损坏的表可能无法恢复全部数据,只能从好的数据页中获取数据。
操作前请行备份!!!
使用到的工具:
dbsake: https://blog.csdn.net/weixin_45385457/article/details/141135847
ibd2sql: https://blog.csdn.net/weixin_45385457/article/details/141135987
关于实战用法请查看我的其他文章,关于详细信息请官网查看。
安装恢复工具
安装 dbsake
# 下载
curl -s get.dbsake.net > dbsake
# 添加执行权限
chmod u+x dbsake
# 添加到 bin 目录
mv dbsake /usr/local/bin/
# 验证
dbsake --help
安装 ibd2sql
# 安装python 3
yum install -y python3
# 下载安装包
wget https://github.com/ddcw/ibd2sql/archive/refs/heads/main.zip
# 解压
unzip main.zip
# 进入安装目录
cd ibd2sql-main
导入表名到 mysql
新建一个相同版本的MySQL
实例,将表结构导入到新实例中。
获取 innodb 表的建表语句
dbsake frmdump $(ll *.ibd|awk '{print $NF}'|awk -F. '{print $1}'|awk '{print $1".frm"}') >/root/create_table.sql
将表结构导入到 MySQL
# 删除并创建库 # 导入表结构
mysql -S /tmp/mysql.sock -e "drop database test;create database test;use test;";mysql -S /tmp/mysql.sock test < /root/create_table.sql
注意:
导入的库名最好和原库名相同
报错处理:
修改导出 SQL 中的 bug (导入报错时使用)
# 默认值无效
# 修改 datetime 类型
sed -i 's#datetime DEFAULT NULL#datetime null DEFAULT NULL#g' create_table.sql
# 修改 timestamp 类型
sed -i 's#timestamp DEFAULT NULL#datetime null DEFAULT NULL#g' create_table.sql
释放表空间导入表
在新实例中释放表空间,然后将旧实例的表空间复制到新实例中,导入表空间。
手动实现
-
1、新实例释放表空间
mysql -e "alter table table_name discard tablespace;"
-
2、将旧表结构移到新表结构库目录中
cp -a /old/data_dir/database/table_name.ibd /new/data_dir/database/table_name.ibd
-
3、授权为 mysql 用户
chown -R mysql:mysql /new/data_dir/database/table_name.ibd
-
4、导入表空间
mysql -e "alter table table_name import tablespace;"
脚本实现
以下脚本实现了自动释放表空间和导入旧表空间的功能,并将导入失败的表写入到 ibd_failed_table.log
文件中,用以下一步的恢复。需要一个库一个库的恢复。
cat ibd_import.sh
#!/bin/bash
# 确保 mysql 连接这里在命令行执行时能获取到表名
mysql_conn="mysql -S /tmp/mysql.sock " # mysql 连接信息
mysqladmin_conn="mysqladmin -S /tmp/mysql.sock ping" # mysqladmin 检测 MySQL 是否正常
mysql_database='test' # 恢复的库名
ibd_save_path=/data1/mysql-5.6.16-winx64/data_old/test # 老的ibd文件保存的目录
recovery_path=/opt/mydata/test # 恢复到的新实例的库路径
# 循环库中所有的表
for table_name in $(${mysql_conn} -N -e "use ${mysql_database};show tables;"|cat);do
# 检查 MySQL 连接是否正常
attempt=0
while [ $attempt -lt 3 ]; do
if ! ${mysqladmin_conn} >/dev/null 2>&1; then
echo "尝试连接 $((${attempt} + 1))次 MySQL 连接消失!!!"
attempt=$((${attempt} + 1))
sleep 10
else
break
fi
if [ $attempt -eq 3 ];then
exit
fi
done
# 将释放表空间sql写入到文件
echo "alter table ${mysql_database}.${table_name} discard tablespace;" >ibd_tmp.sql
# 执行写入 并检查报错 报错时此表跳出循环
if ! ${mysql_conn} ${mysql_database} < ibd_tmp.sql 2>>ibd_err.log ;then
# 打印失败的表名
echo "${table_name} 释放表空间失败"
echo "${table_name}" >>ibd_failed_table.log
# 失败时删除表
${mysql_conn} -e "use ${mysql_database}; drop table ${table_name};"
continue
fi
# 将旧表 ibd 文件移到新的库中
# mv "${ibd_save_path}"/"${table_name}".ibd "${recovery_path}"/
cp -a "${ibd_save_path}"/"${table_name}".ibd "${recovery_path}"/
# 授权用户
chown -R mysql:mysql "${recovery_path}"/
chmod 660 "${recovery_path}"/"${table_name}".ibd
# 将导入表空间sql写入到文件
echo "alter table ${mysql_database}.${table_name} IMPORT tablespace;" >ibd_tmp.sql
# 失败时写入到文件并删除表
if ! ${mysql_conn} ${mysql_database} < ibd_tmp.sql 2>>ibd_err.log;then
echo "${table_name} 导入表空间失败"
echo "${table_name}" >>ibd_failed_table.log
${mysql_conn} -e "use ${mysql_database}; drop table ${table_name};"
rm -f "${recovery_path}"/"${table_name}".ibd
continue
fi
echo "${table_name} 导入成功"
echo "${table_name}" >>ibd_success_table.log
done
echo "脚本执行完成!!!"
执行脚本
sh ibd_import.sh
# 可以使用 nohup & 后台运行防止中断
这一步可以导入一部分表,剩下的基本都是存在坏页的表,这些表的表名保存在 ibd_failed_table.log 中。
ibd 坏表解析成 SQL
将 ibd_failed_table.log 文件中的表,通过 ibd2sql 工具导出成 SQL。
手动实现
ibd2sql 只能解析 mysql8.* 版本的 ibd 文件,所以需要先启动一个 mysql8 的实例,供 ibd2sql 获取元数据信息。具体操作步骤如下:
-
1、创建 mysql8.0实例
略
-
2、使用 dbsake 指定 frm 文件生成建表语句
dbsake -frmdump /old/data_dir/database/table_name.frm >/tmp/table_name_create_table.sql
-
3、导入建表语句到 mysql 8.0
mysql database < /tmp/table_name_create_table.sql
-
4、使用 ibd2sql 生成建表语句
cd ibd2sql-main && python3 main.py /mysql8/data_dir/database/table_name.ibd --ddl >>/tmp/table_name.sql
-
5、使用 ibd2sql 读取数据页生成 sql 语句
vim ibd2sql.sh
#!/bin/bash mysql5_filename="/mysql5/database/table_name.ibd" # 要解析的老 ibd 文件名 mysql8_filename="/mysql8/database/table_name.ibd" # 要解析的mysql8 ibd 文件名 output_path="/tmp/table_name.sql" python3 main.py ${mysql8_filename} --ddl >> ${output_path} # 获取表结构信息 filesize=`stat -c %s ${mysql5_filename}` maxpagecount=$[ ${filesize} / 16384 ] current_page=1 while [ ${current_page} -le ${maxpagecount} ];do echo "-- ${mysql5_filename} ${maxpagecount} PAGE NO: ${current_page}"; current_page=$[ ${current_page} + 1 ] # 解析数据页 写入到文件 timeout 2 python3 main.py --sdi-table ${mysql8_filename} ${mysql5_filename} --sql --mysql5 --page-start ${current_page} --page-count 1 >> ${output_path} done
-
6、导入到 MySQL
mysql database </tmp/table_name.sql
脚本实现
vim ibd2sql.sh
#!/bin/bash
table_name="$1" # 要解析的表名
old_data_path="/data1/mysql-5.6.16-winx64/data_old/test" # 老的数据库路径(保存 ibd 文件的库路径)
mysql8_data_path="/mydata/3309/test" # 新的 mysql8 的 路径(保存 ibd 文件的库路径)
mysql8_conn="/usr/local/mysql/mysql80/bin/mysql -S /mydata/3309/mysql.sock test " # mysql8 连接信息
output_path=/tmp/table_data # 输出 .sql 文件的路径
filename="${table_name}.ibd"
>${output_path}/${table_name}.sql
# dbsake 获取表结构 sed 过滤后 导入 mysql8
/usr/local/bin/dbsake frmdump ${old_data_path}/${table_name}.frm |sed 's#datetime DEFAULT NULL#datetime null DEFAULT NULL#g'|sed 's#timestamp DEFAULT NULL#datetime null DEFAULT NULL#g' ${mysql8_conn}
if [ $? -ne 0 ];then
echo "MySQL 表结构导入失败"
exit
fi
sleep 1
# 指定 mysql8 的 ibd 文件生成建表语句
python3 main.py ${mysql8_data_path}/${filename} --ddl >>${output_path}/${table_name}.sql
# 计算文件大小
filesize=`stat -c %s ${old_data_path}/${filename}`
# 获取数据页数量
maxpagecount=$[ ${filesize} / 16384 ]
current_page=1
# 循环数据页
while [ ${current_page} -le ${maxpagecount} ];do
echo "-- ${filename} ${maxpagecount} PAGE NO: ${current_page}" >>ibd_progress_${table_name}.log;
current_page=$[ ${current_page} + 1 ]
# 指定生成回滚 sql
timeout 2s python3 main.py --sdi-table ${mysql8_data_path}/${filename} ${old_data_path}/${filename} --sql --mysql5 --page-start ${current_page} --page-count 1 >>${output_path}/${table_name}.sql
done
# 执行
sh ibd2sql.sh.sh table_name
并行执行
# 循环读取ibd_failed_table.log中的表名
parallel -j 7 ./ibd2sql.sh :::: /root/test/ibd_failed_table.log
# 后台运行
nohup parallel -j 7 ./test.sh :::: /root/test/ibd_failed_table.log &
# 杀死进程 停止运行
ps -ef | egrep 'python3 main.py|ibd2sql.sh|parallel' | grep -v grep | awk '{print $2}' | xargs -r kill -9
查看进度
# 查看进展文件 对比总数据页和当前数据页
tail -f ibd_progress_${table_name}.log
==> /root/ibd2sql-main/ibd_progress_t_sales_order_detail_v.log <==
-- t_sales_order_detail_v.ibd 449536 PAGE NO: 267213 # 对比页数即可
-- t_sales_order_detail_v.ibd 449536 PAGE NO: 267214
导入表到mysql5 的新实例中
# 登录 mysql
mysql
use database
# 导入数据
source /tmp/table_name.sql
这里如果使用
mysql < sql_file
的方式导入有可能会报错停止。
恢复 MyISAM 表
# 进入旧实例待修复的库中
# 获取 MyISAM 表 并拷贝到数据目录
ll $(ll *.MYD | awk '{print $NF}' | awk -F. '{print $1".*"}')|awk '{print $NF}'|xargs -I {} cp -a {} /mydata/3308/test/
# 检查并修复损坏的表
mysqlcheck -u root -p --all-databases --auto-repair
# 生成 SQL 查看 MyISAM 表行数
ll *.MYD | awk '{print $NF}' |awk -F. '{print "select count(*) from "$1";"}'
恢复视图
导入视图
# 进入旧数据目录
# 导出视图到 mysql5 的新实例
dbsake frmdump *.frm |grep -B 7 'CREATE ALGORITHM=' |mysql
恢复系统表
# 创建恢复库
create database mysql_bak;
# 将表文件复制到库中
cp -a ./mysql/* /new_data_dir/mysql_bak/
# 授权
chown -R mysql:mysql /new_data_dir/mysql_bak/
user 表
# 将数据导出
SELECT * INTO OUTFILE '/tmp/user_data.sql' FROM mysql_bak.user;
# 删除不想导入的用户
vim /tmp/user_data.sql
# 导入到系统表
LOAD DATA INFILE '/tmp/user_data.sql' INTO TABLE mysql.user;
proc 表
# 将数据导出
SELECT * INTO OUTFILE '/tmp/proc_data.sql' FROM mysql_bak.proc;
# 导入到系统表
LOAD DATA INFILE '/tmp/proc_data.sql' INTO TABLE mysql.proc;
其他表
略 都一样
检查并备份
以上步骤完成后检查所有的表,并备份整个实例,然后新建一个实例恢复进去,或者执行 mysql_upgrade 命令。
检查并修复 mysql5 新实例的表
mysqlcheck -u root -p --all-databases --auto-repair
利用 mysql5 的 mysql_upgrade 命令可以修复系统表的一些错误(非必选)
mysql_upgrade -u root -p
全备
mysqldumpe -A -R -E --triggers --master-data=2 --single-transaction >all.sql
本文章仅供参考,请验证后使用。
标签:数据恢复,name,--,MySQL,ibd,sql,mysql,table From: https://blog.csdn.net/weixin_45385457/article/details/141136121