一、先说一个让人头疼的场景
你是不是也遇到过这种事:公司里跑着一个老系统,数据库就一台,所有的业务模块全都连着同一个库。平时没事还好,一到月底对账、或者做个大促活动,数据库压力一大,备份的时候业务就卡顿,甚至偶尔报错。更要命的是,有一次半夜误操作,把线上表给删了,结果运维翻备份一看——昨天晚上的备份文件是坏的,根本恢复不了。那一刻,估计想死的心都有了。
这其实就是单体架构下共享数据库最常见的痛点:数据全在一个篮子里,备份如果做得不靠谱,那整个公司的数据安全就悬在一条钢丝绳上。所以今天咱们不聊那些高大上的分布式架构,就踏踏实实聊聊,在单体架构里,到底该怎么设计一套靠谱的备份策略,保障那个共享数据库里的数据不出事。
二、先搞清楚共享数据库的“共享”到底意味着什么
2.1 多个业务模块共用一套库
单体应用里,用户模块、订单模块、商品模块,甚至日志模块,都写在同一个项目里,连的也是同一个数据库。这种架构的好处是开发简单、部署方便,但坏处也很明显:一旦数据库挂了,所有业务全挂。备份也一样,因为大家共用一个库,所以备份的范围必须覆盖全库,不能只备份某几张表。
2.2 备份的三大目标:恢复时间、丢失量、完整性
咱们做备份,最终是为了在出事儿的时候能恢复。所以有三个指标必须心里有数:
- RTO(恢复时间目标):从出故障到系统恢复,允许花多久?比如目标是1小时,那备份和恢复方案就得能在一小时内把库拉起来。
- RPO(恢复点目标):最多能容忍丢多少数据?比如目标是5分钟,那就意味着备份频率至少要5分钟一次。
- 完整性:备份文件本身是不是好的?能不能在关键时刻还原成功?
在单体架构里,因为只有一个库,这三个指标反而好做。但很多人恰恰忽略了完整性,导致备份文件坏了都不知道,这比不备份还可怕。
三、单体架构备份的几种主流策略
3.1 物理备份:简单粗暴的“文件复制”
物理备份就是直接把数据库底下的数据文件、日志文件复制一份。最快,也最完整。适合数据库比较大的场景。但缺点是只能恢复到某个时间点,做增量备份的时候需要点技术。
拿 MySQL 来说,最常用的物理备份工具是 xtrabackup,它能在不锁表的情况下完成备份。
# 技术栈:Shell + Xtrabackup
# 全量备份,把整个MySQL数据目录备份到/backup/full/目录
xtrabackup --backup \
--user=backup_user \
--password=backup_pass \
--host=127.0.0.1 \
--target-dir=/backup/full/
# 执行完成后,会在/backup/full/下生成一堆文件
# 其中xtrabackup_checkpoints文件记录了备份的LSN号,这是增量备份的关键
# 做完全量备份后,第二天做一个增量备份
xtrabackup --backup \
--user=backup_user \
--password=backup_pass \
--host=127.0.0.1 \
--target-dir=/backup/inc1/ \
--incremental-basedir=/backup/full/
# 恢复的时候,先把全量备份恢复,再叠加增量备份
xtrabackup --prepare \
--apply-log-only \
--target-dir=/backup/full/
xtrabackup --prepare \
--apply-log-only \
--target-dir=/backup/full/ \
--incremental-dir=/backup/inc1/
注意这段脚本里的注释,全量加增量,恢复时顺序不能乱。物理备份的好处是恢复速度快,坏处是跨平台能力差,换一台不同版本的机器可能就恢复不了。
3.2 逻辑备份:更安全,但恢复比较慢
逻辑备份是用 SQL 语句把数据导出来,比如 MySQL 的 mysqldump。这种备份文件是可读的文本,跨版本、跨平台都能恢复。缺点是恢复速度慢,特别是几百万行的表,导出来大,再导进去更慢。
# 技术栈:Shell + mysqldump
# 全量逻辑备份,导出某个共享数据库的全部数据
# 注意:--single-transaction 适合InnoDB引擎,保证一致性视图,不锁表
mysqldump -h 127.0.0.1 -u backup_user -p'密码' \
--single-transaction \
--set-gtid-purged=OFF \
--routines \
--triggers \
--databases shared_db > /backup/shared_db_$(date +%F).sql
# 压缩备份文件,减少磁盘占用
gzip /backup/shared_db_$(date +%F).sql
# 恢复的时候先解压,再用mysql命令导入
gunzip /backup/shared_db_$(date +%F).sql.gz
mysql -h 127.0.0.1 -u root -p'密码' < /backup/shared_db_2025-01-01.sql
逻辑备份比较适合数据量不大的场景,比如几百 GB 以内。如果数据库已经几个 TB,那用逻辑备份恢复一次可能要半天,RTO 根本没法达标。
3.3 Binlog 增量备份:补上时间缝隙
不管是物理还是逻辑备份,全量备份的频率不可能太高,一般一天一次。那从全量结束到下一次全量开始之间的数据怎么办?这就需要靠数据库的 binlog(二进制日志)来补。
binlog 会记录所有增删改的操作,只要保留 binlog,就能恢复到任意一个时间点。具体做法是:每天凌晨做全量备份,然后实时或定时把 binlog 归档到另一台机器上。
# 技术栈:Shell + mysqlbinlog
# 先把binlog文件从数据库服务器拉到备份服务器
# 假设线上数据库的binlog路径是/var/lib/mysql/mysql-bin.000231
mkdir -p /backup/binlogs/
cp /var/lib/mysql/mysql-bin.000231 /backup/binlogs/
# 用mysqlbinlog把指定时间段的binlog导出成SQL语句
mysqlbinlog --start-datetime="2025-01-01 00:00:00" \
--stop-datetime="2025-01-01 03:00:00" \
/backup/binlogs/mysql-bin.000231 > /backup/recover_20250101.sql
# 把导出的SQL语句恢复到数据库(注意:恢复前最好先恢复全量备份)
mysql -h 127.0.0.1 -u root -p'密码' < /backup/recover_20250101.sql
这样,如果今天上午 10 点数据库崩了,咱们可以把昨晚的全量备份恢复,再把今天 0 点到 10 点之间的 binlog 导出来重新执行一遍,数据就能恢复到崩溃前一秒的状态。
四、备份策略的核心:场景决定方案
4.1 场景一:追求最小数据丢失(金融、订单类系统)
这类业务不允许丢数据,哪怕丢一条订单也不行。那策略得这么做:
- 每天凌晨 1 点做全量物理备份。
- 开启 binlog,并且把 binlog 实时同步到另一台机器(比如用 MySQL 主从复制中的延迟从库,或者直接用 binlog 归档工具)。
- 每 5 分钟做一次增量备份(如果硬件条件允许,也可以直接用 binlog 做时间点恢复)。
这里有个细节:为了保险,建议做“延迟从库”,也就是从库故意延迟同步 1 小时。万一主库被误删,延迟从库上还有一小时前的数据,可以立刻从从库找回。
4.2 场景二:追求恢复速度快(RTO 很短)
比如后台管理系统,不能停机超过半小时。那方案就偏向物理备份加冷备机:
- 每天做一次全量物理备份,同时定期把备份文件复制到一台空闲的备用服务器上。
- 备用服务器上解压好数据文件,一旦主库挂了,直接把备用服务器的数据目录切换过去,几分钟就能恢复。
- 如果不想浪费一台服务器,也可以使用数据库自带的克隆插件,比如 MySQL 的 clone 插件,快速重置一份数据到备用实例。
-- 技术栈:MySQL Clone 插件
-- 在备用实例上执行克隆操作,从主实例拉取数据
-- 注意:需要在备用机上先安装clone插件
INSTALL PLUGIN clone SONAME 'mysql_clone.so';
-- 设置克隆工作目录,必须有写权限
SET GLOBAL clone_valid_donor_list = '主库IP:3306';
-- 开始克隆,会直接把主库的数据文件复制到备用机
-- 这一步是全量物理备份+自动恢复的一体化操作
CLONE INSTANCE FROM 'backup_user'@'主库IP'
IDENTIFIED BY '备份用户密码'
DATA DIRECTORY = '/data/clone_dir';
-- 克隆完成后,备用机就有了和主库一模一样的数据
-- 之后可以手动把服务切过去,或者继续做增量同步
克隆插件很适合做“物理冷备机”,因为它不会影响主库性能,而且比传统搬文件更快。
4.3 场景三:预算有限,合租服务器
很多小公司就一台云主机,数据库和代码都在上面,没那么多钱搞备用机。那也得有备份意识,至少做到:
- 每天凌晨用 mysqldump 全量备份,然后压缩,传到对象存储(比如阿里云 OSS、腾讯云 COS)上。
- 每天备份的对象存储保留 7 天,每周的保留一个月。
- 开启 binlog,并且每天把 binlog 也传到对象存储留存。
# 技术栈:Shell + Cron + 云存储工具
#!/bin/bash
# 这是Cron任务,每天凌晨2点执行
# 先做全量备份
mysqldump -h 127.0.0.1 -u backup_user -p'密码' \
--single-transaction \
--all-databases > /tmp/all_db_$(date +%F).sql
# 压缩
gzip /tmp/all_db_$(date +%F).sql
# 上传到对象存储
# 使用腾讯云COS的cli工具,也可以用阿里云OSS的ossutil
coscmd upload /tmp/all_db_$(date +%F).sql.gz /backup/db/
# 删除本地7天前的备份,防止磁盘占满
find /tmp/all_db_*.sql.gz -mtime +7 -delete
# 同时把当天的binlog也上传
# 注意:binlog一般比较多,可以只保留当天的
for log_file in /var/lib/mysql/mysql-bin.*; do
coscmd upload "$log_file" /backup/binlog/$(date +%F)/
done
# 上传完成后删除本地的binlog旧文件,保留一天即可
find /var/lib/mysql/mysql-bin.* -mtime +1 -delete
这种方案成本最低,但恢复时间会长一些,可能需要下载备份文件再导入,适合非核心业务。
五、备份验证:千万别让备份成为“心理安慰”
很多团队做了备份,但从来没验证过。直到出事故才发现备份文件损坏,或者恢复流程根本不顺。所以备份策略里必须包含“验证”这个环节。
5.1 定期做恢复演练
每个月至少做一次恢复演练。找一台临时的数据库实例,把最新备份导进去,然后启动业务,跑几个关键查询,看看数据是否完整。
# 技术栈:Python + MySQLdb
# 这是一个简单的恢复演练脚本,核心是自动化的验证流程
import subprocess
import MySQLdb
import sys
# 第一步:下载最新备份文件(假设备份在本地路径)
backup_file = "/backup/shared_db_latest.sql.gz"
# 第二步:解压备份文件
print("正在解压备份文件...")
subprocess.run(["gzip", "-d", "-k", backup_file], check=True)
# 第三步:用mysql命令把备份导入到临时恢复实例
# 假设我们在本机启动了一个临时MySQL实例,端口3307
print("正在导入备份到临时实例...")
import_cmd = "mysql -h 127.0.0.1 -P 3307 -u root -p'临时密码' < {}.sql".format(backup_file)
subprocess.run(import_cmd, shell=True, check=True)
# 第四步:连接临时实例,做几个关键查询
try:
conn = MySQLdb.connect(host="127.0.0.1", port=3307,
user="root", passwd="临时密码",
db="shared_db")
cursor = conn.cursor()
# 检查订单表有没有数据
cursor.execute("SELECT COUNT(*) FROM orders")
order_count = cursor.fetchone()[0]
print("订单表数据条数:", order_count)
# 检查用户表
cursor.execute("SELECT COUNT(*) FROM users")
user_count = cursor.fetchone()[0]
print("用户表数据条数:", user_count)
# 如果两个表都有数据,说明备份基本可用
if order_count > 0 and user_count > 0:
print("恢复演练成功!备份文件可用。")
else:
print("警告:数据表为空,备份可能有问题!")
sys.exit(1)
except MySQLdb.Error as e:
print("连接临时数据库失败:", e)
sys.exit(1)
finally:
conn.close()
这个脚本只是示例,生产环境里可以加上更复杂的校验逻辑,比如比对关键业务表的行数,或者检查最大ID是否合理等。
5.2 监控备份任务的状态
备份任务本身也可能失败,所以要给备份加监控。可以用 shell 脚本在备份完成后判断 exit code,也可以把备份日志推送到监控系统(比如 Zabbix、Prometheus + Alertmanager)。
# 技术栈:Shell + 监控告警
#!/bin/bash
# 备份失败时,发送告警到企业微信机器人
backup_cmd="mysqldump --single-transaction ..."
if $backup_cmd; then
echo "备份成功" >> /var/log/backup.log
# 也可以把备份成功的消息发送到企业微信群里
curl -X POST 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=你的key' \
-H 'Content-Type: application/json' \
-d '{"msgtype": "text", "text": {"content": "数据库备份成功"}}'
else
echo "备份失败" >> /var/log/backup.log
curl -X POST 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=你的key' \
-H 'Content-Type: application/json' \
-d '{"msgtype": "text", "text": {"content": "数据库备份失败,请检查!"}}'
fi
当然,更省事的方式是直接用 cors 定时任务,在任务执行后自动发送邮件或 webhook 通知。
六、共享数据库备份的几个特别注意事项
6.1 注意备份期间对线上业务的影响
物理备份虽然不锁表,但会占用大量磁盘 IO。共享数据库压力本来就大,再叠加备份任务,很容易拖垮业务。所以建议:
- 备份时间段选在业务低峰期,比如凌晨 2-4 点。
- 备份时设置 IO 限流,比如 xtrabackup 的
--throttle参数。 - 避免备份任务和应用热更新同时进行。
# 技术栈:Xtrabackup
# 限速备份,限制每秒读写512KB,减轻磁盘IO压力
xtrabackup --backup \
--throttle=512 \
--user=backup_user \
--password=备份密码 \
--target-dir=/backup/full/
6.2 注意备份文件的加密
共享数据库里很可能有用户手机号、地址等敏感信息。备份文件一旦泄露,就是重大安全事故。所以备份文件必须加密存储。
# 技术栈:Shell + OpenSSL
# 对备份文件进行AES256加密
openssl enc -aes-256-cbc -salt -pbkdf2 \
-in /backup/shared_db.sql \
-out /backup/shared_db.sql.enc \
-k '你的加密密码'
# 解密的时候
openssl enc -d -aes-256-cbc -pbkdf2 \
-in /backup/shared_db.sql.enc \
-out /backup/shared_db_decrypted.sql \
-k '你的加密密码'
当然,也可以直接用云厂商的对象存储加密功能,或者用 gpg 加密。
6.3 注意备份保留周期
备份不是越久越好。保留太多会把磁盘塞满,恢复时文件太多也麻烦。我的建议是:
- 每日全量备份保留 7 天。
- 每周全量备份保留 1 个月。
- 每月全量备份保留 1 年。
- binlog 至少保留 7 天,这样既能保证数据安全,又不会占用太多空间。
6.4 注意“共享数据库中多个业务表之间的数据一致性”
因为多个模块共用一个库,备份时一定要保证所有模块的表是同一个时间点的快照,不能出现用户表是今天的,订单表却还是昨天的。所以备份时用 --single-transaction 或 xtrabackup 的一致性快照功能很关键,这样才能保证全局一致性。
万一某些表用了非事务引擎(比如 MyISAM),那备份时就得锁表,会导致短暂的写阻塞。建议这种情况下尽早把表结构改成 InnoDB。
七、完整的备份策略示例:一套可以落地的方案
我把上面所有知识点汇总成一套具体方案,假设我们是一家使用 MySQL 的单体架构公司,共享数据库叫做 shop_db,数据量约 200GB,每天订单量约 1 万。
7.1 备份工具选择
- 全量备份:使用
xtrabackup做物理备份,速度快,对业务影响小。 - 增量备份:每天晚上做一次增量,基于前一天的全量或增量。
- binlog 归档:实时同步 binlog 到备份服务器。
- 恢复验证:每周在测试环境自动恢复一次。
7.2 备份计划表
时间点 任务
------------------------------------------------
02:00 全量备份(周日)/增量备份(其他天)
03:00 上传备份到云存储
04:00 自动验证昨天备份的完整性
每分钟 同步binlog到备份服务器
7.3 对应脚本示例
# 技术栈:Shell + Xtrabackup + 云存储
#!/bin/bash
# 这是增量备份脚本,每周日做全量,其他天做增量
DAY_OF_WEEK=$(date +%u) # 周日=7
BACKUP_BASE="/backup/db"
BINLOG_DIR="/backup/binlog"
# 如果今天是周日,做全量备份
if [ "$DAY_OF_WEEK" -eq 7 ]; then
FULL_DIR="$BACKUP_BASE/full_$(date +%F)"
xtrabackup --backup \
--user=backup_user \
--password=backup_pass \
--target-dir="$FULL_DIR" \
--throttle=512
# 全量备份完成后,删除7天前的全量备份
find "$BACKUP_BASE" -maxdepth 1 -type d -name "full_*" -mtime +7 -exec rm -rf {} \;
else
# 非周日,做增量备份
# 先找到最近一次全量备份目录
LAST_FULL=$(ls -d "$BACKUP_BASE"/full_* | tail -n1)
INC_DIR="$BACKUP_BASE/inc_$(date +%F_%H%M)"
xtrabackup --backup \
--user=backup_user \
--password=backup_pass \
--target-dir="$INC_DIR" \
--incremental-basedir="$LAST_FULL" \
--throttle=512
# 清理3天前的增量备份
find "$BACKUP_BASE" -maxdepth 1 -type d -name "inc_*" -mtime +3 -exec rm -rf {} \;
fi
# 上传今天的备份文件到云存储
coscmd upload "$BACKUP_BASE" /backup/remote/ --recursive
# 把binlog也上传,保留7天
coscmd upload "$BINLOG_DIR" /backup/remote/binlog/ --recursive
恢复的时候,按以下流程操作:
- 准备全量备份。
- 叠加所有增量备份(按时间顺序)。
- 复制回数据目录。
- 启动 MySQL。
- 再通过 binlog 恢复到指定时间点。
# 技术栈:Shell + Xtrabackup 恢复流程
# 第一步:准备全量备份,把日志重放
xtrabackup --prepare --apply-log-only --target-dir=/backup/full_2025-01-01
# 第二步:依次叠加增量备份
xtrabackup --prepare --apply-log-only \
--target-dir=/backup/full_2025-01-01 \
--incremental-dir=/backup/inc_2025-01-02
xtrabackup --prepare --apply-log-only \
--target-dir=/backup/full_2025-01-01 \
--incremental-dir=/backup/inc_2025-01-03
# 第三步:最后一步不再加apply-log-only,保证数据一致性
xtrabackup --prepare --target-dir=/backup/full_2025-01-01
# 第四步:把数据目录复制到MySQL的数据目录
rsync -avP /backup/full_2025-01-01/ /var/lib/mysql/
# 第五步:设置权限并启动MySQL
chown -R mysql:mysql /var/lib/mysql
systemctl start mysql
# 第六步:如果需要恢复到更精确的时间点,用binlog补数据
mysqlbinlog --start-datetime="2025-01-03 00:00:00" \
--stop-datetime="2025-01-03 11:32:16" \
/backup/binlog/mysql-bin.000245 | mysql -u root -p
八、备份策略的优缺点对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 物理备份 | 恢复快、对业务影响小 | 跨版本恢复困难、文件体积大 |
| 逻辑备份 | 文件可读、跨平台好恢复 | 恢复慢、备份慢 |
| binlog 增量 | 能做到秒级恢复 | 需要额外管理、时间长了文件多 |
| 云存储归档 | 成本低、安全、异地容灾 | 需要额外下载、网络带宽要求 |
| 备机热备 | RTO 极短 | 成本高、需要额外维护 |
没有完美的方案,只有适合你的方案。建议中小公司先做逻辑备份 + binlog,数据量大了再升级到物理备份 + 备机。
九、给不同基础的开发者一些建议
9.1 如果你是刚入行的开发者
先学会用 mysqldump 做全量备份,再学会用 binlog 恢复数据。不要一开始就搞复杂的物理备份。多看备份日志,了解备份耗时和数据量之间的关系。
9.2 如果你是运维或高级开发
掌握 xtrabackup 的使用,特别是增量备份和恢复的参数。把备份流程写成交互式脚本,加上监控和告警。定期做恢复演练,别把备份当成摆设。
9.3 如果你是架构师
考虑引入“备份中心化”思路:所有备份文件和 binlog 都统一传输到独立备份服务器,避免单点故障。甚至可以在备份服务器上再每天做一次快照,双重保险。
十、文章总结
单体架构下共享数据库的数据安全,看似简单,实际处处是坑。备份不是跑一次脚本就完了,而是要形成一套完整的策略:全量 + 增量 + binlog 三层配合,再加上定期验证和加密存储,才能真正做到“事发可恢复”。实际落地时,根据自己的业务量、预算、团队能力,选择合适的组合方案。记住,最贵的不是备份工具,而是数据丢失后的代价。哪怕再麻烦,也请务必把备份这件事做到位,毕竟数据没了,故事也就没了。
评论
围绕“单体架构的备份策略,该如何保障其共享数据库的数据安全”参与讨论