【MySQL核心】MySQL无法启动?批量恢复MySQL 物理文件-拯救即将跑路的你

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
全局流量管理 GTM,标准版 1个月
简介: 【MySQL核心】MySQL无法启动?批量恢复MySQL 物理文件-拯救即将跑路的你

简介

本文章主要讲解 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

全备

mysqldump  -A -R -E --triggers --master-data=2  --single-transaction >all.sql

本文章仅供参考,请验证后使用。


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
7天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
本文介绍了MySQL InnoDB存储引擎中的数据文件和重做日志文件。数据文件包括`.ibd`和`ibdata`文件,用于存放InnoDB数据和索引。重做日志文件(redo log)确保数据的可靠性和事务的持久性,其大小和路径可由相关参数配置。文章还提供了视频讲解和示例代码。
113 11
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
|
1月前
|
SQL 关系型数据库 MySQL
|
1月前
|
存储 关系型数据库 MySQL
PACS系统 中 dicom 文件在mysql 8.0 数据库中的 存储和读取(pydicom 库使用)
PACS系统 中 dicom 文件在mysql 8.0 数据库中的 存储和读取(pydicom 库使用)
30 2
|
1月前
|
SQL 存储 关系型数据库
SQL文件导入MySQL数据库的详细指南
数据库中的数据转移是一项常规任务,无论是在数据迁移过程中,还是在数据备份、还原场景中,导入导出SQL文件显得尤为重要。特别是在使用MySQL数据库时,如何将SQL文件导入数据库是一项基本技能。本文将详细介绍如何将SQL文件导入MySQL数据库,并提供一个清晰、完整的步骤指南。这篇文章的内容字数大约在
132 1
|
2月前
|
存储 关系型数据库 MySQL
深入解析MySQL数据存储机制:从表结构到物理存储
深入解析MySQL数据存储机制:从表结构到物理存储
157 1
|
2月前
|
SQL 关系型数据库 MySQL
Python小技巧——将CSV文件导入到MySQL数据库
Python小技巧——将CSV文件导入到MySQL数据库
46 0
|
3月前
|
安全 关系型数据库 MySQL
揭秘MySQL海量数据迁移终极秘籍:从逻辑备份到物理复制,解锁大数据迁移的高效与安全之道
【8月更文挑战第2天】MySQL数据量很大的数据库迁移最优方案
651 17
|
3月前
|
存储 SQL 关系型数据库
mysql体系结构及主要文件
了解MySQL的体系结构和它的主要文件,能够帮助数据库管理员和开发者更好地管理和优化数据库。这对于数据库的正常运行、性能优化、数据恢复和系统维护来说是至关重要的。通过对这些组件的深入理解,可以更容易地解决数据库问题,并在必要时定制或调整数据库的行为以满足特定的应用需求。
62 0
|
3月前
|
关系型数据库 MySQL Windows
MySQL数据导入:MySQL 导入 Excel 文件.md
MySQL数据导入:MySQL 导入 Excel 文件.md
|
4月前
|
存储 关系型数据库 MySQL
面试题MySQL问题之FastDFS中的文件重复上传如何解决
面试题MySQL问题之FastDFS中的文件重复上传如何解决
43 1