ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Windows 下 MySQL 误删数据恢复:binlog、备份与表空间打捞

Windows 下 MySQL 误删数据恢复:binlog、备份与表空间打捞 凌晨两点接到电话说生产库被人执行了一条不带 WHERE 的 DELETE或者更狠一点有人手一抖把整个 schema 拖进了回收站。绝大多数人第一反应是重启 MySQL 服务、装个数据恢复软件扫一遍硬盘这两个动作恰恰是最容易把数据彻底弄没的。Windows 下恢复被删除的 MySQL 数据库真正的难点不在用什么工具而在于误删发生后的头一个小时里你有没有做对判断、有没有及时停写、有没有选对恢复路线。这篇内容面向运维、后端开发、小团队的技术负责人以及自己搭 MySQL 做业务系统的朋友我会把 binlog 反转、逻辑备份还原、.ibd 表空间打捞、磁盘层页级恢复这几条路线拆开讲透每一步都说明白为什么这么做以及我在实际事故里踩过哪些坑。1. 误删发生后的第一个小时先判断走哪条路再动手事故现场最忌讳的就是手比脑子快。我先讲一个真实场景客户反馈订单列表一夜之间空了运维同学第一时间重启了 MySQL然后开始全库 mysqldump等找到我时binlog 已经被后续写入冲掉了一部分撤销窗口彻底关闭。所以下面这三件事必须在动手前解决。1.1 先排除假删除连错实例、连错库、权限被改我遇到过不下五次的所谓数据库被删了最后发现是连接连错了地方。Windows 上特别常见的情况是机器里同时装了 MySQL 5.7 和 MySQL 8.0 两个服务两个都设了开机自启端口一个 3306 一个 3307客户端工具里保存的连接指向了那个闲置实例当然一张表都看不到。还有几种同类现象账号权限被人改过之后SHOW DATABASES里少了库但数据其实还在库名大小写不一致在 Windows 上默认不区分大小写迁到别的地方就出问题应用配置指向了测试环境的同库名。确认身份只需要一条 SQL在怀疑的实例上跑SELECT version, port, datadir, hostname, log_bin, binlog_format;重点看port和datadir。如果datadir指向的不是你以为的那个目录那说明你连的根本不是出事的那套。这一步花三十秒能省掉后面几个小时的无用功。1.2 停写这件事要分情况什么时候必须立刻停服务这是全文最关键的一段请务必区分两类事故。第一类是逻辑误删也就是通过 SQL 执行的DELETE、DROP TABLE、TRUNCATE数据文件还在原地。这时候的原则是优先保住 binlog不要急着重启服务。原因在于 MySQL 重启时会做两件对恢复不利的事一是 redo 前滚和 undo 回滚会修改 .ibd 文件的物理内容二是重启后后台 purge 线程会继续推进被删除记录占用的空间会被新写入的行复用。同时也不建议立刻做全库 mysqldump那个动作会产生大量顺序读和临时文件写入进一步挤压恢复窗口。正确的顺序是立刻在应用侧断开写入切到只读、停掉定时任务然后把 binlog 文件先复制一份到别的盘再开始分析。第二类是文件层误删比如有人清理磁盘时删掉了 Data 目录下的 .ibd或者整个数据目录被清空、被同名脚本覆盖。这时候要反过来做立刻停服务防止 InnoDB 启动时对残缺的文件做写操作。sc query type service state all | findstr /i mysql net stop MySQL80停服务之后把整个数据目录原封不动复制一份到另一块盘后续所有分析都在副本上做原始目录在拿到结论前不要碰。1.3 三条恢复路线的适用条件对照先用手头有什么来倒推能走哪条路比盲目试工具高效得多。手头资源典型适用场景恢复粒度大致成功率主要风险binlog 完整且为 ROW 格式DELETE/UPDATE 误操作可精确到秒级操作高binlog 已被清理、格式为 STATEMENT全量逻辑备份 binlogDROP/TRUNCATE 整表整库备份点 增量重放高备份点太旧、备份文件其实是空的只剩 .ibd / .frm 文件数据目录还在但字典损坏表级中页空间被复用、表结构对不齐磁盘被格式化或文件被覆盖文件系统层丢失尽力打捞低覆盖写、磁盘加密我的经验是能走 binlog 就别走文件恢复能走备份就别走 binlog 反转。binlog 反转的准确率取决于你对事务边界的理解而备份还原是确定性操作风险低得多。真正需要靠 .ibd 打捞的时候通常意味着前两条路都断了那时候拼的是时间和运气。2. 用 binlog 把误删操作倒着执行一遍binlog 是 MySQL 自带的操作录像带只要它还在绝大多数误删都能救回来。MySQL 8.0 默认开启 binlog 且格式为 ROW这对恢复非常有利。2.1 三行命令确认 binlog 还在、格式对不对SHOW VARIABLES LIKE log_bin; SHOW VARIABLES LIKE binlog_format; SHOW BINARY LOGS;log_bin为 ON、binlog_format为 ROW是最理想的组合。SHOW BINARY LOGS会列出所有 binlog 文件和大小注意看最后一个文件的时间戳如果最近的写入还在说明窗口没被冲掉。顺手确认一下当前位点MySQL 8.0 上还能用SHOW MASTER STATUS8.4 之后改名成了SHOW BINARY LOG STATUS如果你用新版本发现旧命令报错别慌就是这个原因。ROW 格式的好处是每一行变更都记录了完整的列值镜像before image 和 after image所以 DELETE 的记录能被完整还原成 INSERT。如果是 STATEMENT 格式binlog 里只有原始 SQL 文本一条DELETE FROM orders WHERE status3你没法直接反推被删了哪几行只能寄希望于数据还在别处。这也是为什么我一直建议生产环境保持 ROW 格式的原因。2.2 用位点还是用时间定位误删窗口的两种方式定位窗口有两种手段。一种是用SHOW BINLOG EVENTS IN binlog.000023逐条看事件找到那条DELETE FROM对应的 Pos 和 End_log_pos另一种是按时间范围截取。实际操作里我更推荐按时间因为更快先问清楚是谁在哪个时间点执行了什么然后把窗口前后各放宽十分钟。SHOW BINLOG EVENTS IN binlog.000023 LIMIT 30;一个细节是如果误删发生在半夜而 binlog 是按大小滚动的你可能需要翻好几个文件mysqlbinlog支持一次传多个文件但必须保证顺序正确最好是按binlog.index里的顺序拼接或者干脆按时间范围跨文件处理。2.3 手工导出与反转从 mysqlbinlog 输出到可执行 SQL导出这一步在 Windows 上有两个坑一是 cmd 重定向的编码问题二是带空格路径的引号处理。我的做法是用--result-file让 mysqlbinlog 自己写文件避开重定向C:\Program Files\MySQL\MySQL Server 8.0\bin\mysqlbinlog.exe --no-defaults ^ --base64-outputDECODE-ROWS -vv ^ --start-datetime2024-05-20 01:55:00 ^ --stop-datetime2024-05-20 02:05:00 ^ --result-fileD:\recover\raw.sql ^ C:\ProgramData\MySQL\MySQL Server 8.0\Data\binlog.000023--base64-outputDECODE-ROWS是关键参数不加的话 ROW 事件会以 base64 密文形式输出人根本读不了。加-vv会额外输出列的类型注释方便你确认每一列对应什么。打开生成的文件你会看到这样的结构### DELETE FROM shop.orders后面跟着### WHERE再下面是一串### 11001 /* INT meta0 nullable0 is_null0 */。这些n就是列的位置序号一个不漏地抄下来把 DELETE 改写成 INSERT把 WHERE 里的赋值改写成 VALUES就得到了反向 SQL。反转规则可以记成一张表原操作反向操作关键注意点DELETEINSERTWHERE 里的每个 n 都要还原成对应列值INSERTDELETE需要主键或唯一键才能精确删除UPDATE用 before image 更新回去必须同时对照 before/after 两段镜像DROP / TRUNCATE无法反转只能回到备份点重放 binlog手工改 SQL 适合几十行以内的场景。如果误删了上万行手改会崩溃这时候就该上工具了。2.4 binlog2sql 这类解析工具在 Windows 上怎么跑binlog2sql 是社区里用得比较多的开源解析工具能直接把 binlog 转成正向和反向 SQL。Windows 上跑它需要先准备 Python 环境装完 Python 3.8 以上版本后pip install pymysql然后python binlog2sql.py -h127.0.0.1 -P3306 -uroot -p你的密码 -d shop -t orders ^ --start-filebinlog.000023 ^ --start-datetime2024-05-20 01:55:00 ^ --stop-datetime2024-05-20 02:05:00 -B D:\recover\rollback.sql-B表示生成回滚语句去掉就是正向语句先跑一次正向确认解析出来的操作和你预期的一致再跑-B生成反向。这里有个非常常见的报错Authentication plugin caching_sha2_password cannot be loaded原因是 MySQL 8.0 默认用caching_sha2_password认证而你装的 pymysql 版本太老。升级 pymysql 到最新版或者单独建一个用mysql_native_password的临时账号来连接都行后者更快。提示解析工具会全表扫描 binlog 里对应表的所有事件如果 binlog 文件有几 GB第一次跑可能要几分钟别以为是卡死了。2.5 回放阶段最容易翻车的四个细节拿到 rollback.sql 之后不要直接往生产库上灌先在一个临时实例上验证确认行数对得上。回放前后分别统计关键表的行数如果差得太远说明解析出的反向操作不完整可能是--start-datetime定晚了把窗口往前挪。关掉 binlog 写入。回放前执行SET SQL_LOG_BIN0;否则这次补救操作又会被写进 binlog把后面的分析全部污染。这个开关是会话级的只影响当前连接。关掉外键检查。回放顺序未必符合外键约束SET FOREIGN_KEY_CHECKS0;打开回放完记得改回来。处理 GTID 冲突。如果开启了 GTID 模式mysqlbinlog 输出的 SQL 里会带SET SESSION.GTID_NEXT...直接导入会因为事务号重复而失败。用mysqlbinlog --skip-gtids重新生成一份或者在回放会话里执行SET SESSION sql_log_bin0让 GTID 不参与两种方式效果等价。事务顺序要反着来。如果多条事务交叉执行回滚也要按相反顺序从最后一条事务往前回放否则可能出现主键冲突或中间状态。最后别忘了修正自增值ALTER TABLE orders AUTO_INCREMENT 最大值1;否则新插入的数据可能撞上刚恢复回来的旧主键。3. 有备份的情况Windows 上还原纯逻辑备份的完整动作有备份不代表能还原这句话我在复盘中说过很多次。备份文件能不能用取决于你有没有真的演练过一次。3.1 先确认备份是什么类型逻辑、物理还是复制文件夹在 Windows 环境下常见的备份形式有三种。第一种是 mysqldump 或图形客户端导出的 SQL 文件属于逻辑备份还原就是重新执行 SQL。第二种是直接把 data 目录整个复制走属于冷备物理备份还原是把文件放回去再启动服务。还有一种更粗糙的做法是数据库停着的时候用文件同步工具镜像目录这个其实和第二种是一回事但对文件被占用的处理要更小心。需要提醒的是XtraBackup 这类热备工具官方只发布 Linux 版本的包Windows 上要做在线热备通常靠从库做备份、或者用商业工具不要指望在 Windows 上装一个 XtraBackup 就万事大吉。如果你的备份策略建立在我以为 Windows 上也有这个工具的基础上请立刻去检查一下备份文件到底存不存在。3.2 mysqldump 还原在 Windows 命令行的三个实操坑还原本身很简单难的是 Windows 命令行那些奇葩行为。第一个坑是 PowerShell 不支持输入重定向。你敲mysql -uroot -p D:\backup\shop.sql会直接报The operator is reserved for future use。解决办法是要么切到 cmd 里执行要么在 mysql 客户端内部用 source 命令。我一般推荐后者因为它对编码和路径的处理更可控。SET NAMES utf8mb4; SOURCE D:/backup/shop_20240520.sql;注意路径用正斜杠反斜杠在 MySQL 客户端里会被当成转义符source D:\backup\shop.sql十有八九报文件找不到。第二个坑是中文乱码。导出时加了--default-character-setutf8mb4导入时也必须带上否则备份文件里存的是 utf8mb4 字节流导入连接按 gbk 解释中文全变问号。还有一种情况是文件本身没问题但你用记事本打开另存过一次BOM 头被写进去了导入时报第一条语句语法错误这时候用十六进制编辑器看一下文件开头有没有EF BB BF有的话用支持无 BOM 保存的编辑器重新存一遍。第三个坑是大文件导入超时。几个 GB 的 SQL 导入到一半报MySQL server has gone away原因是单条 INSERT 太大超过了max_allowed_packet或者导入时间太长触发了net_read_timeout。可以在导入前临时调整服务端参数导完再改回来。另外导入过程中临时把innodb_flush_log_at_trx_commit调成 2、会话里关掉UNIQUE_CHECKS和FOREIGN_KEY_CHECKS速度能快好几倍代价是导入期间如果断电可能损坏反正你是在恢复数据天塌下来也就重来一次。3.3 只丢一张表把单表从全量备份里单独拎出来全量备份有 40GB你只想恢复其中一张 200MB 的表怎么办不要想着用文本工具去截 SQL 片段太容易截断出错。更稳的做法是三步走。先把备份还原到一个临时库。如果备份是用--databases shop生成的文件里有CREATE DATABASE和USE shop语句直接改成shop_tmp会比较麻烦更省事的办法是还原到本机另一个测试实例完全不改动文件内容。mysql -uroot -p -e CREATE DATABASE shop_tmp DEFAULT CHARACTER SET utf8mb4; mysql -uroot -p shop_tmp D:/backup/shop_20240520.sql然后在生产库和临时库之间做数据搬运只搬需要的那张表INSERT INTO shop.orders SELECT * FROM shop_tmp.orders WHERE order_id NOT IN (SELECT order_id FROM shop.orders);如果表很大直接 INSERT SELECT 可能撑爆 undo 空间那就按主键分段搬每次一两个月的数据中间留出缓冲。搬完做一次行数和主键最大值比对确认数据完整最后再删掉临时库。注意mysqldump --single-transaction的备份点对 InnoDB 表是一致的但如果库里混了 MyISAM 表那张表就没有一致性保证。恢复前想清楚这一点别把不一致的数据当基准。3.4 用计划任务把备份跑起来脚本、账号与权限备份这件事靠人手动执行迟早会断。Windows 上可以用计划任务加批处理下面这份脚本可以直接改路径用echo off chcp 65001 nul set BINC:\Program Files\MySQL\MySQL Server 8.0\bin for /f %%i in (powershell -NoProfile -Command Get-Date -Format yyyyMMdd_HHmm) do set TS%%i set DIRD:\mysql_backup if not exist %DIR% mkdir %DIR% %BIN%\mysqldump.exe --defaults-extra-fileD:\scripts\backup.cnf ^ --single-transaction --routines --triggers --events ^ --set-gtid-purgedOFF --default-character-setutf8mb4 shop ^ %DIR%\shop_%TS%.sql if %ERRORLEVEL% NEQ 0 echo BACKUP FAILED %DATE% %TIME% %DIR%\error.log forfiles /p %DIR% /m shop_*.sql /d -14 /c cmd /c del path密码不要写在脚本里放到独立的配置文件再用 NTFS 权限锁死[client] userbackup_user password这里填密码 host127.0.0.1 default-character-setutf8mb4icacls D:\scripts\backup.cnf /inheritance:r /grant:r SYSTEM:(R) Administrators:(R)计划任务有两个容易忽略的点。一是默认的以 SYSTEM 运行访问不了网络共享因为 SYSTEM 是机器本地账号跨网络认证会变成匿名如果你的备份目标是网络盘必须换成有权限的域账号或本机账号并勾选不管用户是否登录都要运行。二是%ERRORLEVEL%判错只能发现命令失败发现不了备份出来是空文件建议再补一条检查确认文件大小大于 100KB否则视为失败。4. 只剩数据文件从 .ibd 和 frm 里把表捞出来这是所有路线里最考验耐心的一条。前提是你的数据目录还在只是数据字典坏了、实例起不来或者数据字典文件被删了而那些 .ibd 文件还在。4.1 冷拷贝把现场原封不动冻起来第一个动作不是修是抄。停服务之后把整个数据目录复制到另一块物理盘robocopy C:\ProgramData\MySQL\MySQL Server 8.0\Data E:\coldcopy /E /COPY:DAT /R:1 /W:1 /MT:16robocopy的返回码 0 到 7 都表示成功7 表示有文件被跳过或时间戳不一致不要看到非零返回码就以为拷贝失败。另外要确认拷贝前后文件数量一致别在 MySQL 还在跑的时候去复制那样拿到的是一份自相矛盾的快照。拷贝完成后我会在副本上先做一次完整性摸底用 MySQL 自带的innochecksum检查页校验C:\Program Files\MySQL\MySQL Server 8.0\bin\innochecksum.exe --page-type-summary E:\coldcopy\shop\orders.ibd它会把各类页的数量列出来如果大部分页校验失败说明文件本身已经损坏后面导入表空间的尝试基本可以放弃了。4.2 表结构从哪里找回来要导入表空间你必须有和原表完全一致的表结构。表结构从哪来取决于 MySQL 版本。MySQL 5.7 及更早版本每个表都有一个 .frm 文件可以用mysqlfrm或dbsake frmdump解析出建表语句。mysqlfrm有一个--diagnostic模式不需要连接数据库就能读mysqlfrm --diagnostic E:\coldcopy\shop\orders.frmMySQL 8.0 取消了 .frm表结构存进了 mysql.ibd 里的数据字典。好消息是 8.0 提供了一个专门的工具可以直接从 .ibd 文件里把序列化的字典信息SDI读出来C:\Program Files\MySQL\MySQL Server 8.0\bin\ibd2sdi.exe E:\coldcopy\shop\orders.ibd D:\recover\orders_sdi.json输出的 JSON 里包含列名、类型、字符集、排序规则、索引定义等信息照着它手写 CREATE TABLE 语句比重头猜靠谱得多。这是我在这类事故里用得最顺手的一个工具很多人不知道它的存在。如果连 mysql.ibd 也没了那就只剩三条路翻代码仓库里的 migration 脚本、找历史备份里的建表语句、从应用层的 ORM 映射反推。这时候建表语句的准确性直接决定导入的成败。4.3 DISCARD / IMPORT TABLESPACE 的完整操作与报错对照准备好表结构后操作分四步。假设要恢复的是rescue库里的orders表CREATE DATABASE IF NOT EXISTS rescue DEFAULT CHARACTER SET utf8mb4; USE rescue; CREATE TABLE orders ( order_id BIGINT NOT NULL, user_id INT NOT NULL, amount DECIMAL(10,2) DEFAULT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (order_id) ) ENGINEInnoDB ROW_FORMATDYNAMIC; ALTER TABLE orders DISCARD TABLESPACE;然后停掉服务把orders.ibd复制到rescue库对应的目录下启动服务执行导入ALTER TABLE orders IMPORT TABLESPACE;这里有几个前提必须满足innodb_file_per_table必须是 ON8.0 默认就是 ON5.6 之前不是页大小必须一致一般默认 16K字符集和行格式最好完全对齐。如果手上还有当初FLUSH TABLES ... FOR EXPORT生成的 .cfg 元数据文件一并拷过去成功率最高没有 .cfg 的话能不能成不同小版本的行为有差异以错误日志为准不要凭记忆下结论。报错对照表这些是我实际撞见过的报错信息常见原因排查方向ERROR 1808 Schema mismatch表结构、列顺序、字符集或行格式不一致用 ibd2sdi 输出逐列比对重点看 ROW_FORMATERROR 1812 Tablespace is missing文件名或位置不对DISCARD 没执行确认库目录下文件名严格为 表名.ibdERROR 1030 Got error 168文件被占用、权限不足或页损坏确认服务已停再拷文件看 error logERROR 1815 Internal error数据字典未识别到该表空间检查 innodb_directories 配置ERROR 2013 Lost connection导入大表耗时超时临时调大 net_read_timeout4.4 MySQL 8.0 去掉 .frm 之后恢复难度难在哪很多人以为 8.0 比 5.7 更先进恢复也应该更简单实际情况正好相反。5.7 时代每个表的元数据独立放在 .frm 里表结构文件丢了只影响那张表8.0 把所有表的元数据集中放进 mysql.ibd这个文件一旦损坏整个实例的几百张表可能同时失联而它们的 .ibd 文件全都好端端躺在那里。所以我在 8.0 环境下的建议是定期单独备份一次表结构用mysqldump --no-data把整个实例的 DDL 导出成一个几 MB 的文件。这个文件在正常时候没用出事的时候能救一个团队一整周。mysqldump -uroot -p --no-data --routines --triggers --all-databases D:\backup\schema_only.sql5. 数据目录被覆盖或磁盘被格式化磁盘层面的抢救顺序走到这一步说明文件系统层面已经出事了。这一节讲的是怎么在数据已经消失的情况下尽最大可能把 InnoDB 的页捞回来。5.1 三条铁律停写、别原地装软件、别让 MySQL 再启动NTFS 删除文件的本质是把 MFT 里对应的记录标记为可复用文件占用的磁盘簇在那之后随时可能被新数据覆盖。所以在这个阶段任何写入都是在赌博。第一条铁律是不在目标盘上安装任何恢复软件。下载、安装、解压、生成临时文件全都是写操作其中任何一步都可能吃掉你要找的数据。正确做法是把硬盘拆下来挂到另一台机器上或者用只读方式连接USB 转接线加写保护。第二条是不往目标盘写备份、日志、页面文件。Windows 的虚拟内存默认会往多个盘写如果 D 盘参与其中那就把 D 盘的页面文件关掉系统属性里的虚拟内存设置或者干脆先拔盘。第三条是别让 MySQL 再启动一次。服务一启动InnoDB 就会尝试打开表空间、写 redo、做恢复流程这些动作都会往数据目录里写东西。诊断期间只做只读操作必要时把数据目录整个搬到另一台机器再研究。5.2 先薅免费的卷影副本、文件历史、回收站动手扫盘之前先把 Windows 自带的几个后悔药找一遍成本几乎为零。卷影副本是最有价值的。如果这台机器开过系统保护或者装有利用 VSS 的备份软件盘符上可能存有历史快照。在资源管理器里右键盘符选属性看有没有以前的版本标签页命令行也可以查vssadmin list shadows如果列表里有误删之前时间点的快照直接挂载出来把整个 Data 目录拷走这是最快也最完整的恢复方式甚至不需要停业务去重装。文件历史记录默认只针对用户文件夹数据盘一般不在保护范围内但值得看一眼控制面板。回收站则要泼一盆冷水在资源管理器里删的文件会进回收站但 MySQL 通过 SQL 执行 DROP 删掉的是表空间文件、进程自己解开的句柄走的不是资源管理器路径回收站里啥也没有。命令行执行 del 同理。我见过太多人在回收站里翻半天最后发现方向就是错的。5.3 页级打捞能做到什么程度如果上面这些都没有就只能做页级打捞。InnoDB 的页是 16KB 固定大小页内按聚簇索引组织记录。只要页本身没被覆盖就可以用工具把里面的记录逐条抠出来。社区里有一套经典工具叫 undrop-for-innodb核心是两个程序一个负责把 .ibd 或 ibdata 文件切成一个个页另一个负责解析页内的记录并输出成 TSV。这套工具主要在 Linux 环境编译使用Windows 上可以通过 WSL 或者临时开一台 Linux 机器来跑把盘挂上去只读处理。打捞的边界必须说清楚。如果删除发生在几天前业务又一直在跑那被删的记录占用的空间极大概率已经被 purge 线程回收、被新数据覆盖能捞回来的可能只有零星的碎片。我经手过的一个案例是周六凌晨误删、周一上午才发现最终只从页里捞回了不到 15% 的记录而且字段有残缺要靠业务侧的对账数据补齐。反过来如果删除后立刻停机捞回八九成是现实的。一个提高成功率的技巧打捞前先把磁盘做成完整镜像比如用 dd 或取证工具做 raw 镜像所有分析在镜像上做。这样即使第一轮工具用错了方法还能换一种思路重来不至于把唯一的现场毁掉。5.4 交给专业团队之前要准备的信息清单如果决定找专业数据恢复把这些信息提前整理好能省下大量沟通时间也直接影响报价和成功率磁盘或整机的 raw 镜像文件不要只给数据目录MySQL 的确切版本号和innodb_page_size的值有人为了性能把页大小改成 8K 或 4K恢复工具必须匹配这个参数是否有 binlog 文件残留有的话一并提供完整的表结构 DDL哪怕是过期的版本磁盘是否启用了 BitLocker 或其他加密加密的一律要能提供解锁凭据否则谁也读不出来误删的准确时间点以及之后这块盘上发生过的所有写入行为包括系统更新、临时文件、日志写入6. innodb_force_recovery 与服务端参数的应急用法这一节针对的是另一种处境数据文件还在但实例因为页损坏起不来你需要一个能启动的壳把数据导出来。6.1 六个级别分别解决什么问题innodb_force_recovery是一个从 1 到 6 的整数数字越大跳过的东西越多能启动的概率越高但数据一致性风险也越大。级别作用适用现象1忽略损坏页继续运行报错日志里出现页校验失败2阻止后台主线程操作启动时后台 purge 崩溃3不回滚未提交事务启动卡在 undo 回滚阶段4不做 insert buffer 合并实例转只读表空间读取异常5不扫描 undo 日志启动提示找不到 undo 表空间6不做 redo 前滚前几级都起不来时的最后手段使用方法是改 my.ini[mysqld] innodb_force_recovery1然后重启服务从 1 开始试不行再加一级。每次重启前先去看错误日志Windows 上一般在数据目录下的主机名.err文件里里面会写清楚上次启动失败的具体原因照着原因加级别比盲目往上加高效得多。6.2 用它把数据导出来而不是把它修好这是我要重点强调的观念。innodb_force_recovery不是修复工具它只是让你能启动一个不完整的实例。级别 6 跳过 redo 前滚意味着你可能读到时间上互相矛盾的页导出来的数据必须逐表校验。正确的流程是用 force_recovery 启动实例立刻用--single-transaction导出需要的库和表把导出的 SQL 拿到一台干净的实例上还原做数据一致性检查检查通过后再考虑替换生产。这台临时实例用完就整个删掉不要在它上面继续跑业务。还有一个误区要澄清如果连启动都进不去是因为数据文件被删了、找不到 .ibd那 force_recovery 帮不上任何忙它只在页级损坏的场景下有效。mysqldump -uroot -p --single-transaction --skip-lock-tables --routines --triggers rescue D:/recover/rescue_full.sql6.3 应急启动时的配置与还原顺序实战里我一般会临时加这么一组参数让实例更容易起来、也更容易把数据抱出来[mysqld] innodb_force_recovery2 skip-networking0 max_allowed_packet256M net_read_timeout600导完之后第一件事是把innodb_force_recovery改回 0 并重启否则实例可能长期处于只读状态后面你会收到一堆表不能写的诡异反馈。我见过有人忘了改回去两周后同事建新表失败才发现这个坑挺尴尬的。7. 让下一次误删不再致命几道真正有效的防线恢复技术再熟也不如一开始就不出事。这一节讲的是我在 Windows 环境下实际落地过的几个措施。7.1 备份的三层结构全量、增量、binlog只做每日全量是远远不够的因为 RPO 可能长达 24 小时。我习惯按三层来配每日一次全量 mysqldump 保留 14 到 30 天binlog 保留 7 到 30 天通过binlog_expire_logs_seconds控制默认值是 30 天相当于 2592000小实例磁盘紧张时很多人会调小但如果你把 binlog 调成 1 天那误删隔天发现就只能哭了再给重要库加一个延迟从库把同步延迟设成 3600 秒误删后你有整整一小时的黄金窗口去把删除操作拦住。CHANGE REPLICATION SOURCE TO SOURCE_DELAY 3600;延迟从库的原理很朴素它永远比主库慢一小时哪怕主库被清空这一小时内你还能从它上面把数据捞出来然后把复制停掉。我把它称为最后一道物理隔离。7.2 权限收口与操作习惯技术措施之外习惯更重要。业务账号不给 DROP 权限这个已经是基本操作了。给数据库管理单独建账号不要几个人共用 root。连接客户端时打开安全更新模式DELETE和UPDATE不带 WHERE 会被直接拒绝SET SESSION sql_safe_updates 1; SHOW VARIABLES LIKE sql_safe_updates;我个人的习惯是线上要删表一律先改名再删。改成orders_bak_20240520之后观察一周确认没人用它再执行 DROP。这一步多花五分钟能避免九成以上的手滑删库。命令审核制度也值得建立哪怕是个三人小团队线上执行 DDL 前在群里说一句要执行什么、影响哪些表就能拦住一批冲动操作。7.3 Windows 上容易被忽略的三个环境坑Windows 跑 MySQL 有几个特有陷阱和恢复密切相关。第一是数据目录默认在系统盘。C:\ProgramData\MySQL\MySQL Server 8.0\Data这个路径系统盘一旦故障或需要重装数据和系统一起没。迁到独立数据盘很简单停服务、把整个 Data 目录搬过去、改 my.ini 里的datadir、启动。注意不要在数据目录非空的情况下执行mysqld --initialize那会往满目录里塞初始文件反而添乱。第二是杀毒软件的实时扫描。它会锁定 .ibd 文件导致你的冷备脚本拷出半截文件或者备份任务莫名其妙失败。把数据目录和备份目录加入杀毒排除列表这是个几分钟能做完、收益极大的动作。第三是多实例共存。机器上同时有 MySQL 5.7 和 8.0 两个服务并且都自启是误操作的高发场景。用服务名区分清楚客户端里的连接也标注明确别让我以为连的是 3306变成事故的开头。7.4 一份可以直接抄的演练清单最后分享一份我每季度都会执行的检查清单全部做完大约两小时挑一个业务库把最近一次的备份还原到测试实例用行数和关键字段校验值比对确认备份文件能被正常打开压缩包能解压文件大小合理不是 0 字节的空壳确认 binlog 文件还在且覆盖了最近一周确认SHOW VARIABLES LIKE binlog_format是 ROW确认备份账号的密码没有过期密码轮换是最隐蔽的备份失效原因因为它不报错只是备份文件变空确认--no-data的纯结构备份也存了一份位置单独记录随便挑一张表演练一次表空间导入流程把步骤和报错记进文档我个人在实际操作中的体会是恢复能力不取决于你装了多少工具而取决于出事那半小时你能不能忍住不去乱动。先把写停掉、先判断走哪条路、先在副本上做实验这三件事做到了大部分误删都能体面收场而那份每年花两小时做的演练清单会在某个普通的周一早上替你省下一整个通宵。
返回列表