文章目录
事故起因是一个备份插件,灾难扩大源于一次误操作,救援耗时整整两天。这篇文章完整记录了我如何把一套 ibdata1 被手动删除、混合引擎的 WordPress 数据库从废墟里捞回来,并把服务器重建成一套「不会再犯同样错误」的环境。如果你正在搜索 #1031、#1036、#1067、#1812 这些错误码,可以直接跳到「踩坑记录」一节。
一、事故经过:一条完美的灾难链
我的个人博客跑在一台 512MB 内存的小 VPS 上(后文称 Host1,系统 Ubuntu 24.04,MySQL 5.7),网站体量约 20GB,其中绝大部分是历年积累的图片。事故链条是这样的:
- 备份插件失控:WordPress 备份插件 BackWPup(BWP-UP)执行备份任务时失败,临时文件没有被清理,直接把磁盘填满;
- MySQL 崩溃:磁盘空间不足,MySQL 异常停止;
- 致命的误操作:我在排查时误信了网上的教程,手动删除了
ibdata1文件,以为能像某些教程说的那样「重建后恢复正常」——结果是 InnoDB 的数据字典彻底损毁,所有 InnoDB 表全部无法识别; - 雪上加霜:这套数据库是 MyISAM 与 InnoDB 混合引擎,MySQL 日志里只剩下一行冰冷的报错:
[ERROR] InnoDB: Header page consists of zero bytes in datafile:
/usr/local/mysql/var/ibdata1, Space ID:0, Flags: 0
ibdata1 的文件头已经变成全零字节。InnoDB 的「大脑」没了,修复原环境已经没有意义。
二、救援总思路:放弃修复,物理搬迁 + 结构重组
这次救援最关键的一个决策是:不要执着于修好损坏的环境,而是把数据「搬」出来。
核心逻辑是利用 MySQL 两种引擎的物理特性差异:
| 引擎 | 数据存放方式 | 恢复思路 |
|---|---|---|
| MyISAM | 数据即文件(.MYD 数据 + .MYI 索引),不依赖系统表空间 |
直接复制文件到新环境,修复索引即可 |
| InnoDB | 每张表一个 .ibd 文件,但表注册信息在 ibdata1 数据字典里 |
在新环境重建空表,用 DISCARD/IMPORT TABLESPACE 物理挂载旧 .ibd 文件 |
也就是说,虽然「大脑」没了,但每一张表的「身体」(.ibd、.MYD 文件)都还好端端地躺在 /usr/local/mysql/var/数据库名/ 目录里。只要把它们移植到一台正常的 MySQL(后文称 Host2)里重新「接上大脑」,数据就能回来。
三、救援实战:八个步骤
Step 1:隔离数据
把 Host1 上损坏数据库的整个物理文件夹(包含 .frm、.ibd、.MYD、.MYI、.opt 文件)完整拷贝到 Host2,比如放到 /root/myblog_backup/。源目录从此只读,不再做任何写入操作。
Step 2:在新环境重建表结构
不能直接把旧文件夹丢进新 MySQL 的数据目录——必须先让新的 ibdata1「知道」这些表的存在。
在 Host2 上建库(字符集与旧库保持一致):
CREATE DATABASE myblog CHARACTER SET utf8mb4;
然后获取建表语句(DDL)。有两个来源,优先用第二个:
- 解析
.frm文件:使用 MySQL Utilities 里的mysqlfrm工具,可以在没有运行中数据库的情况下离线解析出建表语句:
mysqlfrm --diagnostic /root/myblog_backup/wp_posts.frm
- 用最近的 SQL 备份:如果你有一份近期的
.sql备份(哪怕数据旧一点),直接导入它,表结构就完整重建了。我用的正是上周的备份,省去了解析.frm的麻烦。
此时 Host2 里有了一批「结构正确、数据为空(或数据为旧)」的表。接下来就是「数据掉包」。
Step 3:MyISAM 表——直接物理覆盖
MyISAM 不归 InnoDB 管,恢复最简单:
# 用 Host1 的最新 .MYD/.MYI 覆盖 Host2 上的同名文件
cp /root/myblog_backup/*.MY* /usr/local/mysql/var/myblog/
chown mysql:mysql /usr/local/mysql/var/myblog/*.MY*
chmod 660 /usr/local/mysql/var/myblog/*.MY*
然后在 phpMyAdmin 里对表执行 CHECK TABLE / REPAIR TABLE,数据立即可读。
Step 4:InnoDB 表——DISCARD / IMPORT TABLESPACE
这是整个救援的核心操作,对每一张 InnoDB 表执行四步:
-- 1. 丢弃新表的物理空间(会删除那个空的 .ibd 文件)
ALTER TABLE wp_posts DISCARD TABLESPACE;
# 2. 把 Host1 的旧 .ibd 文件复制过来
cp /root/myblog_backup/wp_posts.ibd /usr/local/mysql/var/myblog/
# 3. 修正属主和权限(漏了这步会报 #1036,见踩坑记录)
chown mysql:mysql /usr/local/mysql/var/myblog/wp_posts.ibd
chmod 660 /usr/local/mysql/var/myblog/wp_posts.ibd
-- 4. 导入表空间,把旧数据「挂载」到新表上
ALTER TABLE wp_posts IMPORT TABLESPACE;
执行成功,这张表的最新数据就救回来了。
Step 5:跳过没价值的表
数据库里有三张表是 InnoDB 的:wp_bwpup_backups(肇事备份插件自己留下的)、wp_layerslider 和 wp_layerslider_revisions(幻灯片插件的缓存表)。这三张表恰好也是导出时报 #1812 - Tablespace is missing 的元凶。
对这类「插件孤儿表」,最稳妥的做法是直接删除、不恢复——反正插件要卸载,缓存可以重建:
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE wp_bwpup_backups, wp_layerslider, wp_layerslider_revisions;
SET FOREIGN_KEY_CHECKS = 1;
Step 6:导出干净的备份
所有表恢复完成后,在 Host2 上导出一份全 InnoDB、结构完整的 .sql 文件。这份文件是「赎金」——它让数据彻底脱离了对物理文件的依赖。
Step 7:重建 Host1
把 Host1 重装为 Debian 12,编译安装干净的 MySQL 5.7,调好 my.cnf(详见第五节的坑)。
Step 8:数据归位
把 Host2 导出的 SQL 导入重建好的 Host1。至此,数据彻底告别损毁的旧环境,救援结束。
四、踩坑记录(错误码速查)
这部分大概是本文对搜索引擎读者最有价值的部分。每一个坑我都实际踩过。
#1031 - Table storage engine for ‘wp_posts’ doesn’t have this option
现象:对 wp_posts 执行 DISCARD TABLESPACE 时报错。
原因:Host2 的 MySQL 是「顺便装的」,没设默认引擎,从 SQL 备份重建出来的表全是 MyISAM——而 MyISAM 根本没有表空间机制,自然不支持 DISCARD/IMPORT。
解决:先把表转成 InnoDB 再操作:
ALTER TABLE wp_posts ENGINE=InnoDB;
预防:在 my.cnf 里加上 default-storage-engine=InnoDB 并重启,避免新表再次建成 MyISAM。可以用 SHOW CREATE TABLE wp_posts; 随时确认一张表的「真实身份」。
#1067 - Invalid default value for ‘post_date’
现象:建表或导入时报默认值非法。
原因:MySQL 5.7 默认开启严格模式(NO_ZERO_DATE),无法容忍 WordPress 老数据里的 0000-00-00 00:00:00 这种「零日期」。
解决:调整 my.cnf 的 sql_mode,去掉严格日期限制:
sql_mode = "NO_ENGINE_SUBSTITUTION"
或者在 phpMyAdmin 的「结构」页把 post_date 等字段的默认值改为 NULL 并勾选允许 NULL。
#1036 - Table ‘wp_posts’ is read only
现象:物理覆盖 MyISAM 文件后,表变成只读,且 chown、chmod 后依然无效。
排查清单(按命中率排序):
- 文件属主/权限:
chown -R mysql:mysql+chmod 660(目录本身要 750); - SELinux 拦截:CentOS/Rocky 系默认开启,即使权限对了也会拦,先
setenforce 0测试; - immutable 属性:用
lsattr检查文件是否被加了i锁(有些备份软件会干这事),有则chattr -i; - MyISAM 的 open count 标记:崩溃时文件处于打开状态,新 MySQL 会认为表已损坏并保护性只读。终极武器是停库后用
myisamchk在物理层修复:
service mysql stop
cd /usr/local/mysql/var/myblog/
/usr/local/mysql/bin/myisamchk -r -f wp_posts.MYI # 重建索引并重置状态标记
chown mysql:mysql * && chmod 660 *
service mysql start
- 磁盘又满了:磁盘满时 MySQL 会把表设为只读自我保护,
df -h看一眼不亏。
#1812 - Tablespace is missing
现象:phpMyAdmin 浏览某些表时报「读取表结构时出错」。
原因:表的注册信息还在,但对应的 .ibd 物理文件已经丢失(或从未成功导入)。
解决:如果是核心业务表,走 DISCARD/IMPORT 流程重新挂载 .ibd;如果是插件孤儿表(本文的 BWP-UP 和 LayerSlider 残留),直接 DROP 掉一了百了。
Space ID mismatch
现象:IMPORT TABLESPACE 时报 Tablespace id is 15 in the file but 1 in the engine。
原因:.ibd 文件内部记录着它在旧环境里的 Space ID(比如 15),而新环境给这张表分配的是另一个 ID(比如 1),两边对不上。
解决:在新环境反复 CREATE TABLE + DROP TABLE 一些临时表,把自增的 Space ID「刷」到与旧文件一致,再执行 IMPORT。网上也有用 hexedit 直接改 .ibd 文件头部的做法,风险较高,只建议作为最后手段。
初始化新 MySQL 失败:Can’t create file ‘ibdata1’ when –innodb-read-only is set
现象:Host1 重装后执行 mysqld --initialize 直接报错退出。
原因:my.cnf 里残留了救援期间加的 innodb_read_only = 1(以及可能的 innodb_force_recovery)。只读模式下禁止创建任何系统文件,初始化必然失败。
解决:删掉(或注释)这两行,清空数据目录,重新初始化:
rm -rf /usr/local/mysql/var/*
/usr/local/mysql/bin/mysqld --initialize --user=mysql --datadir=/usr/local/mysql/var
五、环境重建与加固
数据救回来之后,顺手把 Host1 的环境做了一次彻底加固,几个关键点:
系统层面
- 重装为 Debian 12(比原来的 Ubuntu 24.04 在这台小机上更省心);
- 512MB 内存的机器,2GB Swap 是呼吸机,必须有;
- 磁盘预警常态化:
df -lh保持使用率 80% 以下——这次事故的源头就是磁盘满载。
文件权限
- 网站目录 755、文件 644,属主统一
www:www; wp-config.php设为 600(里面有数据库明文密码);- 用
chattr +i .user.ini锁定 PHP 配置,防止被篡改。
数据迁移传输
- 跨机搬运大量碎小图片时,用
rsync -azP -e "ssh -p 端口",-P的断点续传是灵魂——面对几十万个小文件,「能传完」比「传得快」重要得多; - 磁盘空间紧张的源主机上不要先打包再传,直接 rsync 同步。
六、重建备份体系(这次真的做对了)
事故的元凶是备份,所以备份体系必须重新设计:
策略:冷热分离。 20GB 里,2026 年以前的上传目录是冷数据(静态图片,早已多处异地备份),每年新增图片只有几百 MB。日常自动备份只需要覆盖「数据库 + 当年 uploads」,备份体积从 20GB 缩到 1GB 以内。
插件选型:这次认真对比了两款主流插件——
- UpdraftPlus:最稳定、容错率高,但 SFTP 传输和增量备份是 Premium 付费功能(个人版 $70/首年);
- BackWPup:免费版就支持 SFTP/FTPS,可以按任务(Job)精确排除
uploads/下的旧年份目录,还支持分卷压缩。
最终选了 BackWPup(讽刺的是,肇事者 BWP-UP 正是它的旧版——但问题出在旧版不清理临时文件,新版已修复)。设定 400MB/卷的分卷压缩,防止打包时瞬间占满磁盘。备份目标双保险:Dropbox(云端)+ FTPS(另一台主机)。
一个冷门坑:vsftpd 做 FTPS 传输时握手失败,需要在 vsftpd.conf 里加一行:
require_ssl_reuse=NO
七、血泪总结
- 永远不要手动删除
ibdata1、ib_logfile。网上那些「删掉重建」的教程针对的是特定场景,对普通 WordPress 库来说,这不会解决问题,只会扩大灾情。这次事故最大的损失不是备份插件造成的,而是我自己这一删造成的。 - 磁盘满载是数据库的头号杀手。监控和预警必须前置,别等
df -h100% 才处理。 - 混合引擎是历史包袱。趁这次重建,把 WordPress 所有表统一成 InnoDB,运维复杂度直线下降。
- 救援时先救结构、再换数据,MyISAM 走物理覆盖,InnoDB 走 DISCARD/IMPORT,遇到错误码先查引擎对不对、权限对不对、严格模式对不对——九成的坑都在这三处。
- 备份的有效性要用恢复来验证,且备份任务本身不能成为磁盘杀手:排除冷数据、分卷压缩、监控临时目录,一个都不能少。
- 卸载插件后,记得清理数据库里的孤儿表——它们不但占地方,还可能在关键时刻(比如导出时)给你报一堆
#1812。
后记:这次救援历时两天,最终所有文章、评论、标签数据完整恢复,损失的只有三张本就打算删除的插件表。重建后的 Host1(Debian 12 / MySQL 5.7)至今稳定运行。希望这篇文章能帮到同样站在「数据库无法启动」界面前面手心冒汗的你——别慌,只要 .ibd 和 .MYD 文件还在,数据就还有救。
文章评论