服务器上跑了三年的mysqldump定时任务,备份文件塞满了整个磁盘才发现,50G的压缩包里有一个数据库从第二年开始就已经损坏了,恢复的时候只回来了第一年的数据。一个免费脚本和一套付费备份系统,到底差在哪里?MySQL、PostgreSQL、MongoDB、SQL Server四种数据库的备份工具放在一起,哪种方式最不容易在出事的时候发现备份是废的?
数据库备份最危险的时刻不是你忘了备份,而是你备了三年,出事那天才发现恢复不了。mysqldump导出过程中表被锁了导致数据不完整、压缩包在传输中损坏、二进制日志序号断裂、从库备份没关只读导致数据不一致——每一个坑都能让一份"看起来正常"的备份变成废纸。这篇文章把四种主流数据库的备份工具从原理到脚本到避坑全部拆开,不管你是管一个WordPress博客的MySQL还是管几十个业务系统的混合数据库集群,都能直接找到对应的方案。
一、先搞懂两类备份的技术本质,才知道该选什么工具
所有数据库备份工具本质上只干两件事:要么"抄数据内容",要么"抄数据文件"。抄内容的叫逻辑备份,抄文件的叫物理备份。选错备份类型比选错工具更致命——100G的数据库用mysqldump逻辑备份可能要跑两小时,中间锁表影响业务;用XtraBackup物理备份十分钟搞定,对业务几乎无感知。
逻辑备份 vs 物理备份 七维对比
| 对比维度 | 逻辑备份(SQL导出) | 物理备份(文件拷贝) |
|---|---|---|
| 代表工具 | mysqldump / pg_dump / mongodump / mydumper | XtraBackup / mariabackup / pg_basebackup / 文件系统快照 |
| 备份内容 | 导出为SQL语句或JSON/BSON文本,可直接阅读 | 直接拷贝数据目录下的二进制文件 |
| 速度(100G数据) | 1-3小时,取决于SQL解析效率 | 10-30分钟,受磁盘IO限制 |
| 对业务影响 | 可能锁表(MyISAM全局读锁,InnoDB可配--single-transaction) | 几乎无感知,InnoDB热备份不阻塞读写 |
| 跨版本兼容 | 好,SQL文本可跨大版本恢复 | 差,通常要求同版本或相近版本 |
| 压缩率 | 高,SQL文本+gzip可压到10%-20% | 中等,约为原始大小的50%-70% |
| 增量备份 | 不支持,只能全量导出 | 支持,基于LSN/二进制日志增量 |
选型结论:10G以下的数据库,逻辑备份够用,简单省事;50G以上必须上物理备份+增量,全量逻辑备份的时间成本和锁表风险不可接受;跨版本迁移用逻辑备份,日常灾备用物理备份+binlog增量。大部分生产环境的最佳实践是:物理备份做每日全量+每小时增量,逻辑备份做每周兜底。
二、MySQL/MariaDB:五种备份工具,从免费到企业级
MySQL是国内网站使用率最高的数据库,WordPress、Discuz、各类CMS后台跑的都是它。备份工具的选择也最丰富,但90%的人只用过mysqldump。
五种MySQL备份工具横评
| 工具 | 类型 | 100G备份耗时 | 增量备份 | 价格 | 最适用场景 |
|---|---|---|---|---|---|
| mysqldump | 逻辑备份 | 2-3小时 | 不支持 | 免费,MySQL自带 | 小库(<5G)、跨版本迁移 |
| mydumper | 逻辑备份 | 30-60分钟 | 不支持 | 免费,开源 | 中大型库(5-50G)逻辑备份 |
| XtraBackup | 物理备份 | 10-20分钟 | 支持增量 | 免费,Percona开源 | 大库(>50G)日常备份 |
| mariabackup | 物理备份 | 10-20分钟 | 支持增量 | 免费,MariaDB自带 | MariaDB用户首选 |
| MySQL Shell | 逻辑+物理 | 15-40分钟 | 支持 | 免费,MySQL 8.0+自带 | MySQL 8.0+的现代化方案 |
为什么mydumper比mysqldump快3-6倍?mysqldump是单线程逐表导出,mydumper是多线程并行导出(默认4线程,可配到16线程),对大表效果明显。但mydumper导出的不是标准SQL,恢复时必须用myloader配套恢复,不能直接用mysql命令行导入。
方案A:mysqldump批量备份脚本(适合10G以下小库)
#!/bin/bash# ============ 配置区 ============BACKUP_DIR="/data/backup/mysql"MYSQL_USER="root"MYSQL_PASS="你的密码"MYSQL_HOST="127.0.0.1"KEEP_DAYS=7 # 保留最近7天备份DATE=$(date +%Y%m%d_%H%M%S)# 创建备份目录mkdir -p $BACKUP_DIR/$DATE# 获取所有数据库名(排除系统库)DATABASES=$(mysql -u$MYSQL_USER -p$MYSQL_PASS -h$MYSQL_HOST \-e "SHOW DATABASES;" | grep -v "Database\|information_schema\|performance_schema\|mysql\|sys")TOTAL=0FAILED=0for db in $DATABASES; doFILE="$BACKUP_DIR/$DATE/${db}.sql.gz"echo "[$(date '+%H:%M:%S')] 备份: $db"# --single-transaction: InnoDB不锁表# --routines: 导出存储过程和函数# --triggers: 导出触发器# --set-gtid-purged=OFF: 避免GTID冲突if mysqldump -u$MYSQL_USER -p$MYSQL_PASS -h$MYSQL_HOST \--single-transaction \--routines \--triggers \--set-gtid-purged=OFF \--databases $db | gzip > $FILE; thenSIZE=$(du -h $FILE | cut -f1)echo " 成功: $FILE ($SIZE)"TOTAL=$((TOTAL + 1))elseecho " 失败: $db"FAILED=$((FAILED + 1))fidone# 验证备份完整性:检查每个文件是否可解压且非空echo ""echo "===== 完整性检查 ====="BROKEN=0for f in $BACKUP_DIR/$DATE/*.sql.gz; doif ! gzip -t "$f" 2>/dev/null; thenecho " [损坏] $f"BROKEN=$((BROKEN + 1))fidone# 清理7天前的备份find $BACKUP_DIR -maxdepth 1 -type d -mtime +$KEEP_DAYS -exec rm -rf {} \;# 发送通知(可选:接入企业微信/钉钉/邮件)if [ $FAILED -gt 0 ] || [ $BROKEN -gt 0 ]; thenecho "[告警] 备份异常: 失败$FAILED个, 损坏$BROKEN个"# curl 发送企业微信机器人消息...fiecho ""echo "===== 备份完成 ====="echo "成功: $TOTAL 个数据库"echo "失败: $FAILED 个"echo "损坏: $BROKEN 个文件"
加入crontab每天凌晨3点自动执行:0 3 * * * /bin/bash /opt/scripts/mysql_backup.sh >> /var/log/mysql_backup.log 2>&1
方案B:XtraBackup全量+增量备份脚本(适合50G以上大库)
#!/bin/bashBACKUP_BASE="/data/backup/mysql_xtra"MYSQL_USER="root"MYSQL_PASS="你的密码"DATE=$(date +%Y%m%d)KEEP_FULL=2 # 保留最近2次全量备份# 判断今天周几,周日做全量,其他做增量DOW=$(date +%u) # 1=周一, 7=周日if [ $DOW -eq 7 ]; then# ===== 周日:全量备份 =====FULL_DIR="$BACKUP_BASE/full_$DATE"mkdir -p $FULL_DIRxtrabackup --backup \--user=$MYSQL_USER \--password=$MYSQL_PASS \--target-dir=$FULL_DIR \--compress \--parallel=4if [ $? -eq 0 ]; thenecho "全量备份完成: $FULL_DIR"# 记录这次全量的路径,供后续增量使用echo $FULL_DIR > $BACKUP_BASE/last_full.txtfi# 清理旧全量备份ls -dt $BACKUP_BASE/full_* | tail -n +$((KEEP_FULL + 1)) | xargs rm -rfelse# ===== 周一到周六:增量备份 =====LAST_FULL=$(cat $BACKUP_BASE/last_full.txt 2>/dev/null)if [ -z "$LAST_FULL" ] || [ ! -d "$LAST_FULL" ]; thenecho "无可用全量备份,跳过增量"exit 1fiINCR_DIR="$BACKUP_BASE/incr_$DATE"mkdir -p $INCR_DIRxtrabackup --backup \--user=$MYSQL_USER \--password=$MYSQL_PASS \--target-dir=$INCR_DIR \--incremental-basedir=$LAST_FULL \--compress \--parallel=4echo "增量备份完成: $INCR_DIR (基于 $LAST_FULL)"fi
恢复顺序:先prepare全量备份(应用已提交事务),再逐个apply增量到全量上,最后一次性copy-back恢复。这个过程比mysqldump恢复快得多——100G数据XtraBackup恢复约15-30分钟,mysqldump导入可能要跑一整夜。

三、PostgreSQL:pg_dump三种模式 + Barman持续备份
PostgreSQL在国内的使用率在快速上升,很多新项目开始用PG替代MySQL。PG的备份工具体系比MySQL更统一,但模式选择需要根据场景判断。
pg_dump三种导出模式对比
| 模式 | 命令 | 导出内容 | 恢复方式 | 适用场景 |
|---|---|---|---|---|
| plain | pg_dump dbname > backup.sql | 纯SQL文本 | psql直接导入 | 小库、跨版本迁移 |
| custom | pg_dump -Fc dbname > backup.dump | 压缩二进制格式 | pg_restore恢复 | 中大型库,支持并行恢复 |
| directory | pg_dump -Fd -j4 -f /backup/ dbname | 目录格式,每个表一个文件 | pg_restore并行恢复 | 大库(>50G),追求恢复速度 |
# PostgreSQL批量备份所有数据库(排除模板库)#!/bin/bashBACKUP_DIR="/data/backup/postgresql"DATE=$(date +%Y%m%d_%H%M%S)mkdir -p $BACKUP_DIR/$DATE# 获取所有非模板数据库psql -U postgres -t -c "SELECT datname FROM pg_database WHERE datistemplate = false AND datname != 'postgres';" | \while read db; doif [ -n "$db" ]; thenecho "备份: $db"# custom格式 + 4线程并行 + 压缩级别6pg_dump -U postgres -Fc -j 4 -Z 6 -f "$BACKUP_DIR/$DATE/${db}.dump" $dbfidone# 全局对象单独备份(角色、表空间)pg_dumpall -U postgres --globals-only -f "$BACKUP_DIR/$DATE/globals.sql"
进阶:Barman(PG专业备份工具)。如果PG数据超过100G,推荐用Barman做持续备份+Point-in-Time Recovery。Barman通过流复制实时接收WAL日志,可以恢复到任意时间点,RPO趋近于零。开源免费,由PG核心团队维护。适合对数据丢失容忍度为零的场景。
四、MongoDB:mongodump + 副本集快照
MongoDB的备份和MySQL/PG思路不同——文档数据库没有表结构的概念,备份出来的BSON文件本身就是完整的文档数据。
MongoDB备份方案对比
| 方案 | 原理 | 优点 | 局限 |
|---|---|---|---|
| mongodump | 逻辑导出为BSON | 跨版本兼容、可选择性导出集合、支持--query过滤 | 大库慢、导出时读压力大 |
| 文件系统快照 | LVM/ZFS快照冻结文件系统 | 秒级备份、不增加数据库负载 | 需文件系统支持、恢复需同版本 |
| 副本集隐藏节点 | 在隐藏从节点上做备份 | 不影响主库性能、随时可做 | 需要额外服务器资源 |
# MongoDB批量备份所有数据库#!/bin/bashBACKUP_DIR="/data/backup/mongodb"DATE=$(date +%Y%m%d_%H%M%S)MONGO_URI="mongodb://user:pass@127.0.0.1:27017"mkdir -p $BACKUP_DIR/$DATE# --gzip: 压缩输出# --oplog: 记录导出期间的oplog,恢复时可回放到导出完成那一刻mongodump --uri="$MONGO_URI" \--out=$BACKUP_DIR/$DATE \--gzip \--oplog \--numParallelCollections=4# 恢复命令:# mongorestore --uri="$MONGO_URI" --gzip --oplogReplay $BACKUP_DIR/$DATE
五、面板工具:宝塔/1Panel一键备份,零门槛但要注意默认设置
国内大量网站用的是宝塔面板或1Panel管理服务器,这两款面板都内置了数据库备份功能。操作简单到点三下鼠标,但默认设置有几个坑必须手动改。
宝塔面板备份功能
- 支持MySQL、SQL Server一键备份
- 支持定时任务:每天/每周/每月
- 可设置保留份数(默认3份)
- 支持备份到七牛/又拍云/阿里OSS/FTP
- 一键恢复,无需命令行操作
- 完全免费
1Panel备份功能
- 支持MySQL、MariaDB、PostgreSQL
- 支持定时+手动+备份到云存储
- 备份记录清晰展示文件大小和时间
- 支持SFTP/FTP/OSS/S3等多种存储
- 开源免费,Docker部署
- 适合技术团队自建运维面板
面板备份的三个默认坑(必须手动改)
坑1:备份文件默认存在服务器本地。如果服务器硬盘坏了或被勒索病毒加密,本地备份跟着一起没。必须在面板的"云存储"设置里配置OSS/S3/七牛等远程存储,备份完成后自动上传到云端。
坑2:默认保留3份,覆盖周期太短。如果每天备份一次保留3份,你只有3天窗口期发现数据问题。建议至少保留7份(一周),有条件保留14-30份。
坑3:备份成功不代表能恢复。面板只提示"备份成功",不会验证备份文件是否完整、是否能成功恢复。强烈建议每个月手动恢复一次到测试库验证——这是最容易被跳过但最关键的一步。
六、SQL Server:微软体系下的备份方案
国内用SQL Server的主要是企业ERP、OA等Windows服务器上的业务系统。SQL Server的备份工具被微软捆绑在SSMS里,但也有一些第三方选择。
| 工具 | 类型 | 批量备份 | 价格 | 适合谁 |
|---|---|---|---|---|
| SSMS维护计划 | SQL Server自带 | 通过维护计划向导可批量选择多个数据库 | 免费(Express版不支持) | 标准版及以上用户 |
| T-SQL脚本 | SQL Server自带 | 游标遍历所有数据库执行BACKUP DATABASE | 免费 | 所有版本,需写脚本 |
| Ola Hallengren脚本 | 开源SQL脚本 | 业界最知名的SQL Server维护方案,批量备份+完整性检查+索引维护一条龙 | 免费开源 | SQL Server DBA标配 |
| SQLBackupAndFTP | 第三方商业软件 | 一键批量备份所有库,自动上传到云存储 | 免费版限2个库 | 非DBA,需要简单图形界面 |
SQL Server Express版没有SQL Agent,不能使用维护计划定时备份。解决方案是用Windows任务计划器 + T-SQL脚本,或者直接用SQLBackupAndFTP免费版(限2个库)。

七、Navicat/DBeaver/DataGrip:数据库管理工具的备份能力
很多人日常用Navicat或DBeaver管理数据库,这些工具自带备份功能。但它们的备份能力跟专业备份工具有本质区别。
数据库管理工具的备份能力对比
| 工具 | 支持的数据库 | 批量备份 | 定时任务 | 价格 | 定位 |
|---|---|---|---|---|---|
| Navicat Premium | MySQL/PG/Mongo/SQL Server/Oracle等8种 | 支持批量选择+数据传输 | 支持(计划任务功能) | 约¥4,999永久 或¥1,299/年订阅 | 全能数据库管理 |
| Navicat Premium Lite | MySQL/PG/Mongo/SQL Server等 | 支持,但功能精简 | 不支持 | 免费 | 轻量级日常管理 |
| DBeaver | 几乎所有数据库 | 支持多选导出 | 社区版不支持 | 社区版免费 PRO版$199/年 | 开源替代Navicat |
| DataGrip | 几乎所有数据库 | 支持多选导出 | 不支持 | $199/年(含JetBrains全家桶套餐) | 开发者日常使用 |
关键判断:Navicat/DBeaver/DataGrip的备份功能本质上是"手动触发的导出",不是"无人值守的备份系统"。它们适合开发阶段偶尔导出一份数据,不适合生产环境7×24定时备份。生产环境必须用脚本+crontab或专业备份工具,管理工具只做辅助验证和手动恢复。
八、九个场景的工具搭配方案
| 你的情况 | 推荐工具组合 | 费用 | 核心优势 |
|---|---|---|---|
| 宝塔面板+MySQL小库 | 宝塔计划任务 + 七牛云/OSS远程存储 | ¥0-5/月(存储费) | 零门槛,三下鼠标搞定 |
| MySQL <10G | mysqldump + crontab + gzip压缩 + OSS自动上传 | ¥0 | MySQL自带,零依赖 |
| MySQL 10-50G | mydumper多线程 + crontab + 远程同步 | ¥0 | 比mysqldump快3-6倍 |
| MySQL >50G | XtraBackup全量+增量 + 每周mysqldump逻辑兜底 | ¥0 | 热备份不锁表,增量省空间 |
| PostgreSQL任意规模 | pg_dump -Fd -j4目录格式 + Barman WAL持续备份 | ¥0 | PG原生工具链最强 |
| MongoDB | mongodump --oplog + 文件系统快照(副本集隐藏节点) | ¥0 | oplog保证时间点一致性 |
| SQL Server | Ola Hallengren脚本 + Windows任务计划器 | ¥0 | 业界标准,DBA首选 |
| 混合数据库(多类型) | 每种数据库用原生工具 + 统一Python调度脚本 + rclone上传云端 | ¥0-10/月 | 一个脚本管所有数据库类型 |
| 云数据库(RDS等) | 云厂商自动备份 + 手动导出到本地/其他云做异地容灾 | 通常含在RDS费用中 | 自动管理,但需异地兜底 |
九、Python统一调度脚本:一个脚本管所有类型的数据库
如果你服务器上同时跑着MySQL、PostgreSQL和MongoDB,用这个Python脚本统一调度备份、压缩、上传、清理,比维护三套Shell脚本方便得多。
import osimport subprocessimport gzipimport shutilfrom datetime import datetime, timedeltafrom pathlib import Pathclass DatabaseBackup:"""统一数据库备份调度器"""def __init__(self, backup_root="/data/backup", keep_days=7):self.backup_root = Path(backup_root)self.keep_days = keep_daysself.today = datetime.now().strftime("%Y%m%d_%H%M%S")self.results = {"success": [], "failed": []}def backup_mysql(self, host, user, password, databases, port=3306):"""批量备份MySQL数据库"""for db in databases:out_dir = self.backup_root / "mysql" / self.todayout_dir.mkdir(parents=True, exist_ok=True)out_file = out_dir / f"{db}.sql.gz"cmd = ["mysqldump",f"-h{host}", f"-P{port}",f"-u{user}", f"-p{password}","--single-transaction","--routines", "--triggers","--set-gtid-purged=OFF",db]try:with gzip.open(out_file, "wb") as f:subprocess.run(cmd, stdout=f, stderr=subprocess.PIPE, check=True, timeout=3600)size_mb = round(out_file.stat().st_size / 1024 / 1024, 2)self.results["success"].append(f"MySQL:{db} ({size_mb}MB)")except Exception as e:self.results["failed"].append(f"MySQL:{db} - {e}")def backup_postgresql(self, host, user, password, databases, port=5432):"""批量备份PostgreSQL数据库"""for db in databases:out_dir = self.backup_root / "postgresql" / self.todayout_dir.mkdir(parents=True, exist_ok=True)out_file = out_dir / f"{db}.dump"env = os.environ.copy()env["PGPASSWORD"] = passwordcmd = ["pg_dump",f"-h{host}", f"-p{port}",f"-U{user}", "-Fc", "-j4", "-Z6","-f", str(out_file), db]try:subprocess.run(cmd, env=env, stderr=subprocess.PIPE, check=True, timeout=7200)size_mb = round(out_file.stat().st_size / 1024 / 1024, 2)self.results["success"].append(f"PG:{db} ({size_mb}MB)")except Exception as e:self.results["failed"].append(f"PG:{db} - {e}")def backup_mongodb(self, uri, databases=None):"""备份MongoDB(databases=None则备份所有库)"""out_dir = self.backup_root / "mongodb" / self.todayout_dir.mkdir(parents=True, exist_ok=True)cmd = ["mongodump", "--uri", uri, "--out", str(out_dir), "--gzip", "--oplog"]if databases:cmd.extend(["--db", " ".join(databases)])try:subprocess.run(cmd, stderr=subprocess.PIPE, check=True, timeout=7200)self.results["success"].append("MongoDB:全部数据库")except Exception as e:self.results["failed"].append(f"MongoDB - {e}")def verify_backups(self):"""验证备份文件完整性"""for db_type in ["mysql", "postgresql", "mongodb"]:today_dir = self.backup_root / db_type / self.todayif not today_dir.exists():continuefor f in today_dir.iterdir():if f.stat().st_size == 0:self.results["failed"].append(f"[空文件] {f.name}")elif f.suffix == ".gz":try:with gzip.open(f, "rb") as gz:gz.read(1024) # 尝试读取验证压缩完整性except Exception:self.results["failed"].append(f"[损坏] {f.name}")def cleanup_old(self):"""清理过期备份"""cutoff = datetime.now() - timedelta(days=self.keep_days)for db_type in ["mysql", "postgresql", "mongodb"]:type_dir = self.backup_root / db_typeif not type_dir.exists():continuefor d in type_dir.iterdir():if d.is_dir():mtime = datetime.fromtimestamp(d.stat().st_mtime)if mtime < cutoff:shutil.rmtree(d)print(f"已清理: {d}")def run(self):"""执行全部备份任务"""print(f"===== 备份开始: {self.today} =====")# === 配置你的数据库连接信息 ===self.backup_mysql("127.0.0.1", "root", "mysql密码", ["db1", "db2", "db3"])self.backup_postgresql("127.0.0.1", "postgres", "pg密码", ["mydb"])self.backup_mongodb("mongodb://user:pass@127.0.0.1:27017")self.verify_backups()self.cleanup_old()# 输出结果print(f"\n成功: {len(self.results['success'])} 个")for s in self.results["success"]:print(f" [OK] {s}")if self.results["failed"]:print(f"\n失败: {len(self.results['failed'])} 个")for f in self.results["failed"]:print(f" [FAIL] {f}")print("\n===== 备份结束 =====")if __name__ == "__main__":backup = DatabaseBackup(backup_root="/data/backup", keep_days=7)backup.run()
加入crontab:0 3 * * * /usr/bin/python3 /opt/scripts/db_backup.py >> /var/log/db_backup.log 2>&1
十、五个让备份变废纸的操作
废纸1:备份了三年,从来没恢复验证过。mysqldump在导出过程中如果表结构变更、字符集不匹配、视图依赖断裂,都可能产生"能导出但恢复时报错"的备份文件。每月至少一次恢复验证——恢复到一台测试服务器上,跑一遍完整的业务功能检查。
废纸2:备份文件只存在服务器本地。服务器被勒索病毒加密、硬盘损坏、机房火灾——本地备份全部报销。3-2-1原则是最低底线:至少3份副本、2种不同存储介质、1份异地存放。简单做法:本地保留7天+OSS/S3保留30天+每季度下载一份到本地NAS或移动硬盘。
废纸3:--single-transaction没加,MyISAM表被锁了。mysqldump默认对MyISAM表加全局读锁,如果备份过程中有写入操作,数据可能不一致。即使是InnoDB表,如果不加--single-transaction,不同表之间的数据时间点也不一致。生产环境备份命令里这个参数不是可选项,是必选项。
废纸4:备份文件没压缩,磁盘被撑爆。100G数据库的SQL文本导出可能达到80-120G,压成gzip后约10-20G。不压缩不仅浪费磁盘空间,还会在磁盘满时导致备份进程异常退出,产生不完整的备份文件。
废纸5:云数据库的自动备份太信任,没做异地导出。阿里云RDS、腾讯云CDB都提供自动备份,但云厂商的备份和你的数据在同一个机房(甚至同一个可用区)。机房级故障虽然概率低但不是零。至少每周手动导出一次到另一个云厂商的OSS或本地服务器。
最后说一句
数据库备份这件事,工具本身的技术差异没有想象中那么大——mysqldump免费、XtraBackup也免费、pg_dump免费、mongodump免费,真正的好工具全都不花钱。拉开差距的是三件事:备份策略是否匹配数据库规模(小库逻辑大库物理)、备份后是否验证了可恢复性(不验证的备份等于没备份)、备份文件是否做了异地容灾(3-2-1原则是最低底线)。这三件事做到了,哪怕只用最简单的mysqldump脚本,出问题时也能救回来;这三件事没做到,花几千块买商业备份软件一样可能在出事那天发现恢复按钮点下去报错。
