MySQL Docker 误删恢复:binlog PITR

MySQL Docker 误删恢复:binlog PITR

场景:MySQL 8.0.29 跑在 Docker 里(数据目录 bind-mount),研发误执行批量 DROP TABLE 清库 本手册记录 2026-08-28 bxjl-dev 恢复实战的全部命令,按序执行即可复现 生产容器:mysql8(对外 13396) 临时恢复容器:mysql_rec(对外 13398,用后即删) 误删定位:2026-08-28 10:33:35,binlog.000006 位点 11086236


0. 前置事实确认(先想清楚能不能恢复)

检查项 判定标准 本次实测
binlog 是否开启 log_bin=ON ON
binlog 保留多久 binlog_expire_logs_seconds 30 天
binlog 覆盖窗口 最老文件起点 ≤ 误删时刻 000004 起于 05-13
库的建表语句是否在窗口内 binlog 里有 CREATE TABLE 161 张表全有
误删后有没有人清 binlog RESET MASTER/PURGE

以上任一不满足,就要换成"目录备份拼结构 + binlog 补数据"的组合方案。


1. 侦察:确认 binlog 状态与文件

bash 复制代码
# 容器与挂载信息(确认数据目录映射)
docker inspect -f '{{.Config.Image}} {{range .Mounts}}{{.Source}}->{{.Destination}} {{end}}' mysql8

# binlog 开关、保留策略、文件列表、当前位点
docker exec mysql8 mysql -uroot -p'buEM53OR!' -e "
  SHOW VARIABLES LIKE 'log_bin%';
  SHOW VARIABLES LIKE 'binlog_expire%';
  SHOW BINARY LOGS;
  SHOW MASTER STATUS;"

# 确认目标库当前状态(表数/行数,判断损失范围)
docker exec mysql8 mysql -uroot -p'buEM53OR!' -N -e "
  SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='bxjl-dev';
  SELECT SUM(table_rows) FROM information_schema.tables WHERE table_schema='bxjl-dev';"

要点 :宿主机没有 mysqlbinlog,必须在容器内执行 docker exec mysql8 mysqlbinlog,输出重定向到宿主机文件。


2. 全量解码 binlog → all.sql

bash 复制代码
mkdir -p /tmp/binlogs
# 按 000004 → 000005 → 000006 顺序解码拼接(文件顺序 = 时间顺序,不能乱)
docker exec mysql8 mysqlbinlog \
  /var/lib/mysql/binlog.000004 \
  /var/lib/mysql/binlog.000005 \
  /var/lib/mysql/binlog.000006 > /tmp/binlogs/all.sql
ls -la /tmp/binlogs/all.sql   # 本次约 190MB

禁用 --base64-output=NEVER------它会中途截断输出,造成"binlog 只到某天为止"的假象(本次踩过)。


3. 定位误删点

binlog 里 DDL 不带 库名. 前缀,直接 grep 会漏。用 awk 跟踪 use 上下文,把"时间戳 + 库 + 破坏性语句"打出来:

bash 复制代码
# 扫描所有 DROP TABLE / TRUNCATE,附带时间戳与所属库
awk '/^#26/{ts=$1" "$2} /^use /{use=$0} /DROP TABLE|TRUNCATE/{print NR" | "ts" | "use" | "substr($0,1,100)}' \
  /tmp/binlogs/all.sql > /tmp/binlogs/drops.txt
wc -l /tmp/binlogs/drops.txt

# 只看误删当天的破坏性事件(本次 = 2026-08-28,binlog 行首时间戳形如 #260828  10:33:35)
grep '#260828' /tmp/binlogs/drops.txt | head -n 60

# 按日期统计破坏性事件数量(确认误删集中在哪天)
awk -F'|' '{split($2,a," "); print a[1]}' /tmp/binlogs/drops.txt | sort | uniq -c

本次定位结果

  • 误删 = 2026-08-28 10:33:35~10:33:37 一轮按表名字母序的批量 DROP TABLE IF EXISTS
  • 首个误删 DROP 位于 all.sql 第 3,110,386 行附近
  • 10:47:23 还有第二轮部分误删(同样要截掉)

换算 stop-position

bash 复制代码
LN=3110386   # 首个误删 DROP 所在行(由上面 grep 得出)
# 先人工确认上下文,别盲信行号
sed -n "$((LN-8)),${LN}p" /tmp/binlogs/all.sql

# 取该 DROP 之前最近的 "# at NNN" 位点标记 → 即 binlog.000006 内的重放截止位点
P=$(head -n $LN /tmp/binlogs/all.sql | grep '^# at ' | tail -n 1 | awk '{print $3}')
echo "STOP_POS=$P"    # 本次 = 11086236

4. 搭临时恢复实例(与生产同镜像)

bash 复制代码
# 建空实例(每次重放前必须从干净库开始,避免脏数据)
docker rm -f mysql_rec 2>/dev/null
rm -rf /www/mysql/database/mysql_rec && mkdir -p /www/mysql/database/mysql_rec
IMG=$(docker inspect -f '{{.Config.Image}}' mysql8)
docker run -d --name mysql_rec -p 13398:3306 \
  -e MYSQL_ROOT_PASSWORD='buEM53OR!' \
  -v /www/mysql/database/mysql_rec:/var/lib/mysql $IMG

# 等实例健康(最长 5 分钟)
for i in $(seq 1 30); do
  docker exec mysql_rec mysqladmin -uroot -p'buEM53OR!' ping 2>/dev/null | grep -q alive \
    && { echo "HEALTHY after $((i*10))s"; break; }
  sleep 10
done

# 预建 binlog 会引用到的所有库(它们的 CREATE DATABASE 可能在 binlog 起点之前)
# 注意:bxjl-dev 带连字符,必须用反引号
docker exec -i mysql_rec mysql -uroot -p'buEM53OR!' -e "
  CREATE DATABASE IF NOT EXISTS \`bxjl-dev\`;
  CREATE DATABASE IF NOT EXISTS factory;
  CREATE DATABASE IF NOT EXISTS rentcar_dev;"

5. 生成重放 SQL(分文件截断)

--stop-position 只对最后一个 binlog 文件生效 → 必须分两段生成再拼接:

bash 复制代码
# part1:000004 + 000005 全量(--skip-gtids 去掉 ANONYMOUS GTID,否则报 ERROR 1790)
docker exec mysql8 mysqlbinlog --skip-gtids \
  /var/lib/mysql/binlog.000004 /var/lib/mysql/binlog.000005 > /tmp/binlogs/part1.sql 2>/tmp/binlogs/gen1.err
echo "GEN1_RC=$?"

# part2:000006 截到误删点前一位点
docker exec mysql8 mysqlbinlog --skip-gtids --stop-position=$P \
  /var/lib/mysql/binlog.000006 > /tmp/binlogs/part2.sql 2>/tmp/binlogs/gen2.err
echo "GEN2_RC=$?"

# 拼接
cat /tmp/binlogs/part1.sql /tmp/binlogs/part2.sql > /tmp/binlogs/replay.sql
wc -l /tmp/binlogs/replay.sql   # 本次 311 万行

三项自检(都通过才继续)

bash 复制代码
# ① part2 最后时间戳应恰好在误删点之前(本次期望 ≈ 10:33:2x)
grep -a '^#26' /tmp/binlogs/part2.sql | tail -n 2

# ② part2 中当天日期的破坏性事件数应为 0
awk '/^#26/{ts=$1" "$2} /DROP TABLE|TRUNCATE/{if (ts ~ /#260828/) c++} END{print "today-destructive-count="c+0}' /tmp/binlogs/part2.sql

# ③ 首个被误删的表不应出现在 part2(本次查 base_table,期望 0)
grep -c 'DROP TABLE IF EXISTS `base_table`' /tmp/binlogs/part2.sql

6. 后台重放(大文件必须 nohup)

SSH 通道一旦断开,前台导入会半途而废(本次踩过)。用 nohup + rc 文件轮询:

bash 复制代码
rm -f /tmp/binlogs/apply2.rc /tmp/binlogs/apply2.err /tmp/binlogs/apply2.out
nohup bash -c 'docker exec -i mysql_rec mysql -uroot -p"buEM53OR!" -f \
  < /tmp/binlogs/replay.sql > /tmp/binlogs/apply2.out 2>/tmp/binlogs/apply2.err; \
  echo $? > /tmp/binlogs/apply2.rc' >/dev/null 2>&1 &
echo "apply launched"

# 轮询直到完成(本次约 4.5 分钟)
while [ ! -f /tmp/binlogs/apply2.rc ]; do sleep 30; done
echo "APPLY_RC=$(cat /tmp/binlogs/apply2.rc)"   # 期望 0

# 错误归因:只关心 bxjl-dev 相关错误
grep -i 'ERROR' /tmp/binlogs/apply2.err | grep -v -i 'factory' | head -n 20

-f 的作用:binlog 里其他库(本次是 factory.*)的"表不存在"历史错误直接跳过------与目标库无关,不影响 bxjl-dev。


7. 恢复实例校验

bash 复制代码
# 表数(期望 161 = 106 现役 + 55 历史遗留)
docker exec mysql_rec mysql -uroot -p'buEM53OR!' -N -e \
  "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='bxjl-dev';"

# 总行数(期望 52,499)
docker exec mysql_rec mysql -uroot -p'buEM53OR!' -N -e \
  "SELECT SUM(table_rows) FROM information_schema.tables WHERE table_schema='bxjl-dev';"

# 数据新鲜度(期望覆盖到误删当天)
docker exec mysql_rec mysql -uroot -p'buEM53OR!' -N -e \
  "SELECT MAX(create_time) FROM \`bxjl-dev\`.market_lead;"

# 逐表行数导出(备查,后续与生产 diff)
docker exec mysql_rec mysql -uroot -p'buEM53OR!' -N -e \
  "SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema='bxjl-dev' ORDER BY table_name;" \
  > /tmp/binlogs/rows_rec.txt

8. 回灌生产(先留底,再导入)

bash 复制代码
# ① 线上现状留底(万一要回滚,这里是恢复前状态)
docker exec mysql8 mysqldump -uroot -p'buEM53OR!' --single-transaction bxjl-dev \
  > /www/mysql/database/bxjl-dev-live-before-restore-20260828.sql
ls -la /www/mysql/database/bxjl-dev-live-before-restore-20260828.sql   # 本次 219KB

# ② 从恢复实例导出完整库(含存储过程/触发器/事件)
docker exec mysql_rec mysqldump -uroot -p'buEM53OR!' --single-transaction --routines --events bxjl-dev \
  > /www/mysql/database/bxjl-dev-recovered-20260828.sql
ls -la /www/mysql/database/bxjl-dev-recovered-20260828.sql           # 本次 15.9MB

# ③ 灌回生产 mysql8
docker exec -i mysql8 mysql -uroot -p'buEM53OR!' bxjl-dev \
  < /www/mysql/database/bxjl-dev-recovered-20260828.sql 2>/tmp/binlogs/restore.err
echo "RESTORE_RC=$?"
cat /tmp/binlogs/restore.err    # 期望只有 password 警告

9. 线上终验(逐表 diff)

bash 复制代码
# 生产逐表行数,与恢复实例比对
docker exec mysql8 mysql -uroot -p'buEM53OR!' -N -e \
  "SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema='bxjl-dev' ORDER BY table_name;" \
  > /tmp/binlogs/rows_live.txt
diff /tmp/binlogs/rows_rec.txt /tmp/binlogs/rows_live.txt && echo "== IDENTICAL =="

# 表数 / 总行数 / 新鲜度 三项复核(同第 7 步,换成 mysql8)

10. 清理现场

bash 复制代码
docker rm -f mysql_rec mysql_pitr
rm -rf /www/mysql/database/mysql_rec /www/mysql/database/mysql_pitr /tmp/binlogs

保留产物:/www/mysql/database/ 下的 bxjl-dev-recovered-20260828.sql(恢复快照)与 bxjl-dev-live-before-restore-20260828.sql(回滚留底)。


附录 A:踩坑清单(务必先看)

# 正确做法
1 mysqlbinlog --database=库名 过滤会丢掉行级(RBR)写事件,重放完业务表全空 全量重放,事后单独 dump 目标库
2 binlog 中 DDL 无 库名. 前缀,grep 'DROP TABLE `db`.' 会漏判 awk 跟踪 use 行上下文再匹配
3 --base64-output=NEVER 导致 mysqlbinlog 中途截断 去掉该参数
4 重放报 ERROR 1790 @@SESSION.GTID_NEXT cannot be changed --skip-gtids
5 --stop-position 只对最后一个 binlog 文件生效 分文件生成 SQL 再拼接
6 库名含连字符,CREATE DATABASE bxjl-dev 语法错 反引号 bxjl-dev
7 宿主机没有 mysqlbinlog docker exec <容器> mysqlbinlog
8 大文件前台导入遇 SSH 断开就半途而废 nohup 后台 + rc 文件轮询
9 重放到非目标库历史语句报错导致中断 mysql -f 跳过无关错误

附录 B:误删后黄金 4 条

  1. 不要重启 MySQL(重启无害,但没必要)
  2. 不要执行 RESET MASTER / PURGE BINARY LOGS / FLUSH LOGS ------ 直接撕证据
  3. 不要把数据目录整个覆盖回去(目录备份可能是模糊备份,会引入不一致)
  4. 先冻结写操作 ,立刻把 binlog 文件原样拷贝一份(cp 到安全目录)
相关推荐
南城以南溫暖如初14734 分钟前
从零搭建24小时自助健身系统:技术选型与核心模块实战
java·spring boot·redis·mysql·vue·mybatis
鱼鳞_2 小时前
热门八股-MySQL
数据库·mysql
这个DBA有点耶2 小时前
数据库“家谱”系列之一:关系型数据库的4大核心组件详解
数据库·mysql·架构
Frank_refuel2 小时前
MYSQL【进阶】 -> 索引(了解)
数据库·mysql
wxchyy3 小时前
手把手教你入门云计算(二):这些技术改变了世界,你也能轻松掌握
hadoop·阿里云·docker·云原生·华为云·云计算·运维开发
l1t3 小时前
DeepSeek总结的DuckDB 如何更快地运行递归 CTE
java·开发语言·数据库·mysql·duckdb
名字还没想好☜3 小时前
Prometheus 告警实战:写 alerting rules、用 Alertmanager 做路由分组与抑制
运维·前端·javascript·docker·kubernetes·prometheus
请你吃div4 小时前
Node 后端项目 Docker 自动部署教程(GitHub + 宝塔 + Self-hosted Runner)
后端·docker·node.js
百年੭ ᐕ)੭*⁾⁾4 小时前
Redash部署指南(Windows docker部署)
大数据·windows·docker·数据可视化·redash