从一次数据库恢复事故开始:给自己的 mysql 自动备份与恢复方案
这篇文章不是为了炫耀搭了一套多复杂的数据库系统,而是为了提醒未来的自己:
数据库没有备份的时候,所有“以后再做”都是给未来的自己挖坑。
这次数据库恢复事故之后,决定把 mysql 自动备份、恢复、邮件告警、保留策略和后续恢复流程完整记录下来。
如果未来再次看到这篇文章,希望第一反应不是“这个什么时候做的”,而是:
先确认备份是否正常,再做数据库高风险操作。
一、这次事故让我意识到了什么
之前服务器上的数据库没有建立完善的定时备份机制。
某次数据库数据出现问题以后,需要进行恢复。
当时才发现:
没有最近的完整数据库备份
↓
只能依赖 binlog
↓
需要人工分析 binlog
↓
一点一点恢复数据
↓
恢复过程复杂、耗时,而且存在遗漏风险
虽然最终可以通过 binlog 找回一部分数据,但是整个过程让我意识到:
binlog 不是完整备份的替代品。
binlog 更适合做:
完整备份 + binlog ↓ 时间点恢复
而不是:
没有备份 + 只靠 binlog ↓ 尝试把整个数据库重新拼回来
这两者完全不是一个难度。
二、以后数据库必须遵守的原则
以后自己的服务器数据库至少遵守下面几个原则。
1. 数据库必须自动备份
不能依赖:
想起来了再备份
必须:
定时任务 ↓ 自动执行
2. 备份必须有保留周期
不能每天备份,但是只保留一个文件。
目前采用:
daily 30天 weekly 12周 monthly 12个月
也就是说:
最近30天
↓
每天都有恢复点
最近3个月
↓
每周都有恢复点
最近1年
↓
每月都有恢复点
3. 备份必须能够验证
不能看到:
wechat.sql.gz
就认为备份成功。
必须检查:
mysqldump
↓
gzip
↓
gzip -t
↓
sha256
至少确保文件没有明显损坏。
4. 备份失败必须通知我
自动备份最大的风险不是“没有脚本”。
而是:
脚本早就失败了,但是自己不知道。
因此以后:
备份成功
↓
记录日志
备份失败
↓
立即发送邮件
5. 本地备份不是最终备份
如果:
mysql + 备份文件
全部存在同一块硬盘上。
硬盘坏了以后:
数据库没了 备份也没了
所以最终还需要:
服务器 ↓ 本地备份 ↓ 异地存储 / 对象存储
这部分以后继续完善。
三、最终备份架构
目前设计:
mysql
│
│
mysqldump
│
▼
┌──────────────┐
│ backup.sh │
└──────┬───────┘
│
┌───────────┼───────────┐
│ │ │
▼ ▼ ▼
gzip sha256 日志
│
▼
┌───────────────┐
│ 本地备份目录 │
└───────┬───────┘
│
┌───────┼────────┐
│ │ │
▼ ▼ ▼
daily weekly monthly
30天 12周 12个月
│
▼
失败邮件通知
未来继续增加:
mysql binlog
+
异地对象存储
+
自动恢复测试
四、备份系统目录
最终目录:
/opt/mysql-backup/
│
├── config/
│ └── backup.conf
│
├── scripts/
│ ├── backup.sh
│ ├── restore.sh
│ ├── check.sh
│ └── status.sh
│
├── backup/
│ ├── daily/
│ ├── weekly/
│ └── monthly/
│
└── logs/
└── backup.log
各文件职责:
| 文件 | 作用 |
|---|---|
backup.conf | 数据库、邮件、保留周期配置 |
backup.sh | 执行数据库备份 |
restore.sh | 恢复数据库 |
check.sh | 检查备份情况 |
status.sh | 查看定时任务状态 |
backup.log | 备份执行日志 |
五、安装备份系统
将一键安装脚本保存到服务器,例如:
install_mysql_backup.sh
然后:
chmod +x install_mysql_backup.sh
使用 root 权限执行:
sudo ./install_mysql_backup.sh
注意:
linux 的 root 用户没有设置密码并不影响这个方案。
服务器上的 root 管理方式是:
sudo -i
而 mysql 的账号密码是另外一套东西。
不要把:
linux root
和:
mysql root
混为一谈。
六、mysql 备份账号
不要让备份脚本长期使用 mysql root。
应该创建一个专门的:
mysql_backup
用户。
只给它备份需要的权限,例如:
select show view trigger event lock tables
原则:
备份程序只拥有完成备份所需要的最低权限。
即使未来备份配置泄露,也尽量避免直接暴露 mysql 管理权限。
七、邮件配置
邮件配置用于:
备份失败 磁盘空间异常 恢复测试失败
smtp 配置不要把真实账号、密码写进博客。
统一使用:
smtp_host=your_smtp_host smtp_port=465 smtp_user=your_email@example.com smtp_password=your_smtp_password mail_to=your_email@example.com
例如:
mail_enabled=true mail_to="your_email@example.com" smtp_host="your_smtp_host" smtp_port="465" smtp_user="your_email@example.com" smtp_password="your_smtp_password" server_name="your_server_name"
其中:
smtp_password
应该是邮件服务商提供的:
smtp 授权码
不一定是邮箱网页登录密码。
八、邮件发送测试
正式启用备份之前,先测试邮件。
例如:
echo "mysql备份邮件测试成功" | mail \ -s "mysql backup test" \ your_email@example.com
如果收到邮件,说明邮件链路正常。
一定要先测试。
否则以后真正备份失败的时候:
备份失败
↓
想发邮件
↓
邮件配置本身也失败
↓
自己完全不知道数据库备份已经挂了
这就失去了告警意义。
九、备份执行时间
目前计划:
每天 03:00
使用 systemd timer:
mysql-backup.timer
查看:
systemctl list-timers mysql-backup.timer
应该能看到下一次执行时间。
十、手动执行备份
不要等凌晨第一次测试。
安装完成后立即执行:
sudo systemctl start mysql-backup.service
然后检查:
sudo /opt/mysql-backup/scripts/check.sh
或者:
tail -100 /opt/mysql-backup/logs/backup.log
十一、查看备份状态
执行:
sudo /opt/mysql-backup/scripts/status.sh
查看:
systemd timer 最近执行时间 最近执行结果
也可以:
journalctl -u mysql-backup.service
查看 systemd 日志。
十二、备份文件是什么样的
例如:
/opt/mysql-backup/backup/daily/2026-08-18/
里面可能有:
wechat.sql.gz wechat.sql.gz.sha256 reward.sql.gz reward.sql.gz.sha256
其中:
.sql.gz
是数据库备份。
而:
.sql.gz.sha256
是校验文件。
十三、daily / weekly / monthly 保留策略
daily
每天一个目录:
daily/ ├── 2026-08-18/ ├── 2026-08-17/ ├── 2026-08-16/ └── ...
保留:
30天
weekly
每周产生一个周备份:
weekly/ ├── 2026-08-16/ ├── 2026-08-09/ ├── 2026-08-02/ └── ...
保留:
12周
monthly
每月 1 号产生月备份:
monthly/ ├── 2026-08-01/ ├── 2026-07-01/ ├── 2026-06-01/ └── ...
保留:
12个月
因此当前设计的最长本地保存周期约为 1 年。
十四、清理策略
清理不是简单地:
rm -rf backup/*
而是按照不同层级清理。
daily ↓ 超过30天 ↓ 删除 weekly ↓ 超过84天 ↓ 删除 monthly ↓ 超过365天 ↓ 删除
所以:
最近30天
保留每天的恢复点。
30天~3个月
主要依赖 weekly。
3个月~1年
主要依赖 monthly。
十五、最长能够恢复多久以前?
按照当前策略:
0~30天
↓
每天一个恢复点
30天~3个月
↓
每周一个恢复点
3个月~12个月
↓
每月一个恢复点
因此:
目前最长本地备份恢复周期约为12个月。
但是这里需要特别注意:
这不代表可以精确恢复到一年前任意一个时间点。
因为一年前主要只有 monthly 备份。
如果需要精确恢复到某一分钟,就需要:
完整备份 + binlog
十六、恢复数据库
这是最重要的部分。
假设:
数据库发生误删除或者严重污染。
先查看备份:
sudo /opt/mysql-backup/scripts/check.sh
找到:
/opt/mysql-backup/backup/daily/2026-08-17/wechat.sql.gz
恢复:
sudo /opt/mysql-backup/scripts/restore.sh \ /opt/mysql-backup/backup/daily/2026-08-17/wechat.sql.gz \ wechat
恢复脚本会检查:
文件是否存在 ↓ sha256 ↓ gzip完整性 ↓ 目标数据库
如果目标数据库已经存在,会要求手工输入:
yes
避免误操作。
十七、强烈建议先恢复到临时数据库
不要一上来就覆盖生产数据库。
例如:
sudo /opt/mysql-backup/scripts/restore.sh \ /opt/mysql-backup/backup/daily/2026-08-17/wechat.sql.gz \ wechat_restore_20260817
这样会创建:
wechat_restore_20260817
而原来的:
完全不会受到影响。
然后进入 mysql:
mysql
检查:
show databases;
再检查:
use wechat_restore_20260817; show tables;
重点检查:
用户 系统配置 业务核心表 订单/任务 日志 字典 权限
确认数据正常以后,再决定是否替换生产数据库。
十八、为什么恢复前必须验证?
因为:
备份文件存在
不等于:
备份一定可以恢复
所以至少需要:
gzip -t + sha256
更进一步:
恢复到临时数据库 + 检查关键表
以后应该逐渐实现:
每周自动恢复测试
十九、这次事故以后最重要的恢复思路
以后如果数据库再次发生事故:
不要第一时间:
疯狂分析 binlog
而应该:
第一步: 确认事故发生时间 第二步: 找到事故之前最近的完整备份 第三步: 恢复完整备份 第四步: 如果需要精确恢复 使用 binlog 第五步: 恢复到事故发生前指定时间
即:
完整备份
+
binlog
↓
point-in-time recovery
这才是正确思路。
二十、binlog 不能被忽略
这次事故已经证明:
binlog 很重要。
但:
binlog 不能代替完整备份。
正确关系:
完整备份
+
binlog
=
完整恢复体系
例如:
03:00 完整备份 03:01 03:02 03:03 ... 10:37 binlog 10:38 数据库发生事故
恢复流程:
03:00完整备份
↓
恢复
↓
应用03:00~10:37的binlog
↓
恢复到10:37
这样就可以最大程度减少数据损失。
二十一、未来还要增加异地备份
目前:
mysql ↓ 服务器本地
这只能解决:
误删 数据污染 软件问题
但解决不了:
服务器硬盘损坏 服务器整体损坏 误删整个服务器 勒索/入侵
所以以后一定要继续增加:
服务器 ↓ 本地备份 ↓ 异地对象存储
例如:
腾讯云 cos
或者其他可靠的对象存储服务。
二十二、未来最终目标
最终希望达到:
mysql
│
┌───────────┴───────────┐
│ │
完整备份 binlog
│ │
▼ ▼
gzip压缩 持续记录
│ │
▼ │
sha256校验 │
│ │
▼ │
本地备份 │
│ │
├── daily 30天 │
├── weekly 12周 │
└── monthly 12个月 │
│ │
▼ │
异地对象存储 │
│ │
└───────────┬───────────┘
▼
时间点恢复
│
▼
恢复测试
│
┌───────┴───────┐
▼ ▼
成功 失败
│ │
│ ▼
│ 邮件告警
│
▼
完成
二十三、以后做数据库高风险操作之前必须检查
这是这次事故之后最应该给未来自己的提醒。
如果准备执行:
drop database
或者:
delete from ...
或者:
truncate table ...
或者:
alter table ...
或者进行:
数据迁移 批量更新 批量删除 数据库结构调整 ai自动修改数据库
先问自己三个问题:
1. 最近一次完整备份是什么时候?
sudo /opt/mysql-backup/scripts/check.sh
2. 最近一次备份能不能恢复?
如果不知道:
先恢复测试,再做高风险操作。
3. binlog 是否正常?
如果需要精确恢复:
完整备份 + binlog
缺一不可。
二十四、以后自己的数据库操作原则
给未来的自己:
不要相信“这次应该没问题”。
数据库操作最危险的一句话就是:
应该没问题
正确方式应该是:
先备份 ↓ 确认备份存在 ↓ 确认备份可用 ↓ 再执行高风险操作
尤其是:
drop delete truncate update alter 数据迁移
执行前一定确认。
二十五、常用命令速查
查看备份
sudo /opt/mysql-backup/scripts/check.sh
查看状态
sudo /opt/mysql-backup/scripts/status.sh
手动备份
sudo systemctl start mysql-backup.service
查看定时任务
systemctl list-timers mysql-backup.timer
查看备份日志
tail -100 /opt/mysql-backup/logs/backup.log
查看 systemd 日志
journalctl -u mysql-backup.service
恢复数据库
sudo /opt/mysql-backup/scripts/restore.sh \ 备份文件.sql.gz \ 目标数据库
例如:
sudo /opt/mysql-backup/scripts/restore.sh \ /opt/mysql-backup/backup/daily/2026-08-17/wechat.sql.gz \ wechat_restore
二十六、备份系统的当前参数
| 项目 | 当前配置 |
|---|---|
| 备份方式 | mysqldump |
| 压缩 | gzip |
| 完整性检查 | gzip + sha256 |
| daily | 30天 |
| weekly | 12周 |
| monthly | 12个月 |
| 最长本地保存 | 12个月 |
| 自动执行 | 每天03:00 |
| 调度 | systemd timer |
| 失败通知 | |
| mysql账号 | 专用 backup 用户 |
| 恢复工具 | restore.sh |
| 检查工具 | check.sh |
| 状态工具 | status.sh |
| 异地备份 | 后续增加 |
| binlog | 后续完善 |
| 自动恢复测试 | 后续增加 |
二十七、最后给未来自己的提醒
这次事故真正浪费的不是恢复命令,而是:
之前没有把“备份”当成系统的一部分。
服务器可以重装。
代码可以重新拉。
docker 可以重新部署。
nginx 可以重新配置。
但是:
数据库里的真实业务数据,很多东西是无法重新生成的。
所以以后:
代码可以没有最新版本 配置可以重新写 服务器可以重新装
但是:
数据库必须有备份
而且:
有备份 ≠ 备份成功 备份成功 ≠ 备份可恢复 备份可恢复 ≠ 数据能恢复到需要的时间点
真正完整的体系应该是:
完整备份 + binlog + 异地备份 + 恢复测试 + 失败告警
二十八、给未来自己的最终 checklist
以后任何一次数据库重大操作之前:
- 确认最近一次 daily 备份存在
- 确认备份文件大小正常
- 确认 gzip 校验通过
- 确认 sha256 校验通过
- 确认 binlog 正常
- 高风险操作前再次确认数据库名称
- 不直接在生产数据库执行未经验证的 sql
- 大批量
delete/update之前先select - 能在测试库执行的操作不要直接上生产
- ai 生成的 sql 必须人工检查
- 涉及数据库结构变更前先备份
- 涉及批量数据修改前先备份
- 恢复时优先恢复到临时数据库验证
- 确认恢复结果以后再处理生产数据库
结语
这套备份系统不是为了让数据库永远不会出问题。
而是为了下一次数据库真的出问题的时候:
不慌 ↓ 找到最近备份 ↓ 恢复 ↓ 应用binlog ↓ 恢复到指定时间 ↓ 继续工作
这次事故已经发生过一次了。
希望以后再看到这篇文章的时候,不要再经历第二次。
数据库备份不是“有空再做”的运维任务,而是生产环境最基本的安全措施。
任何可能破坏数据库数据的操作之前,先确认:我能不能恢复?
以上就是mysql自动备份与恢复方案的详细内容,更多关于mysql自动备份与恢复的资料请关注代码网其它相关文章!
发表评论