一、前言 数据是企业的核心资产,数据库备份是保障数据安全的最重要措施。根据 Verizon 2026 年数据泄露调查报告,28% 的数据丢失事件是由于备份失败或恢复失败导致的 。
MySQL 作为全球最流行的开源数据库,提供了多种备份恢复方案:
逻辑备份 :mysqldump、mysqlpump
物理备份 :XtraBackup、MySQL Enterprise Backup
二进制日志 :binlog 增量备份
快照备份 :LVM、云存储快照
主从复制 :实时数据冗余
本文将详细介绍各种 MySQL 备份恢复方案,提供完整的操作步骤和可运行的自动化脚本,帮助你构建可靠的数据保护体系。
二、备份策略设计 2.1 备份类型对比 1 2 3 4 5 6 7 8 9 10 graph TB A[MySQL 备份] --> B[逻辑备份] A --> C[物理备份] A --> D[增量备份] A --> E[快照备份] B --> B1[mysqldump<br/>跨版本<br/>速度慢] C --> C1[XtraBackup<br/>热备份<br/>速度快] D --> D1[binlog<br/>PITR 恢复<br/>需配合全量] E --> E1[LVM/云快照<br/>秒级<br/>平台依赖]
备份类型
优点
缺点
适用场景
逻辑备份
跨版本、可编辑、选择性强
速度慢、恢复慢
小数据量、迁移
物理备份
速度快、支持热备
同版本、文件大
大数据量、生产
增量备份
节省空间、恢复灵活
依赖全量、复杂
频繁备份
快照备份
秒级、一致性
平台依赖
云环境
2.2 备份策略建议 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 gantt title 备份策略示例(周) dateFormat HH:mm axisFormat %H:%M section 全量备份 周日全量 :crit, 01:00, 2h section 增量备份 周一增量 :01:00, 30m 周二增量 :01:00, 30m 周三增量 :01:00, 30m 周四增量 :01:00, 30m 周五增量 :01:00, 30m 周六增量 :01:00, 30m section Binlog 实时备份 :active, 00:00, 24h
推荐策略 :
全量备份 :每周日凌晨 1 点
增量备份 :每天凌晨 1 点(除周日)
Binlog 备份 :实时备份,每小时切割
保留策略 :全量保留 4 周,增量保留 1 周,binlog 保留 1 周
三、逻辑备份(mysqldump) 3.1 基础用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 mysqldump -u root -p mydb > mydb.sql mysqldump -u root -p --databases db1 db2 > backup.sql mysqldump -u root -p --all-databases > all.sql mysqldump -u root -p --no-data mydb > structure.sql mysqldump -u root -p --no-create-info mydb > data.sql mysqldump -u root -p mydb table1 table2 > tables.sql
3.2 推荐参数 1 2 3 4 5 6 7 8 9 10 11 12 mysqldump -u root -p \ --single-transaction \ --quick \ --lock-tables=false \ --set-gtid-purged=OFF \ --triggers \ --routines \ --events \ --hex-blob \ --default-character-set=utf8mb4 \ mydb > mydb.sql
3.3 压缩备份 1 2 3 4 5 6 7 8 mysqldump -u root -p mydb | gzip > mydb.sql.gz mysqldump -u root -p mydb | gpg -c > mydb.sql.gpg mysqldump -u root -p mydb | gzip | gpg -c > mydb.sql.gz.gpg
3.4 恢复数据 1 2 3 4 5 6 7 8 9 10 11 mysql -u root -p mydb < mydb.sql gunzip < mydb.sql.gz | mysql -u root -p mydb gpg -d mydb.sql.gpg | mysql -u root -p mydb mysql> source /path/to/backup.sql;
四、物理备份(XtraBackup) 4.1 安装 XtraBackup 1 2 3 4 5 6 7 8 9 wget https://repo.percona.com/apt/percona-release_latest.generic_all.deb sudo dpkg -i percona-release_latest.generic_all.debsudo apt updatesudo apt install percona-xtrabackup-80sudo yum install https://repo.percona.com/yum/percona-release-latest.noarch.rpmsudo yum install percona-xtrabackup-80
4.2 全量备份 1 2 3 4 5 6 7 8 9 10 11 mkdir -p /backup/mysql/fullxtrabackup --backup \ --target-dir=/backup/mysql/full \ --user=root \ --password=your_password xtrabackup --prepare --target-dir=/backup/mysql/full
4.3 增量备份 1 2 3 4 5 6 7 8 9 10 11 12 13 xtrabackup --backup \ --target-dir=/backup/mysql/incr-mon \ --incremental-basedir=/backup/mysql/full \ --user=root \ --password=your_password xtrabackup --backup \ --target-dir=/backup/mysql/incr-tue \ --incremental-basedir=/backup/mysql/incr-mon \ --user=root \ --password=your_password
4.4 恢复全量备份 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 sudo systemctl stop mysqlsudo rm -rf /var/lib/mysql/*xtrabackup --prepare --target-dir=/backup/mysql/full xtrabackup --prepare --target-dir=/backup/mysql/full \ --incremental-dir=/backup/mysql/incr-mon xtrabackup --prepare --target-dir=/backup/mysql/full \ --incremental-dir=/backup/mysql/incr-tue xtrabackup --copy-back --target-dir=/backup/mysql/full sudo chown -R mysql:mysql /var/lib/mysqlsudo systemctl start mysql
五、Binlog 增量备份 5.1 配置 Binlog 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [mysqld] log_bin = /var/log/mysql/mysql-bin.logbinlog_format = ROWbinlog_expire_logs_seconds = 604800 max_binlog_size = 100 Mbinlog_ignore_db = mysqlbinlog_ignore_db = information_schemabinlog_ignore_db = performance_schemagtid_mode = ON enforce_gtid_consistency = ON
5.2 查看 Binlog 1 2 3 4 5 6 7 8 9 10 11 12 13 mysql -u root -p -e "SHOW BINARY LOGS;" mysql -u root -p -e "SHOW MASTER STATUS;" mysqlbinlog /var/log/mysql/mysql-bin.000001 mysqlbinlog --start-datetime="2026-03-12 10:00:00" \ --stop-datetime="2026-03-12 11:00:00" \ /var/log/mysql/mysql-bin.000001
5.3 Binlog 备份脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #!/bin/bash BACKUP_DIR="/backup/mysql/binlog" DATE=$(date +%Y%m%d_%H%M%S) RETENTION_DAYS=7 mkdir -p $BACKUP_DIR mysql -u root -p$MYSQL_PASSWORD -e "FLUSH BINARY LOGS;" LATEST_BINLOG=$(mysql -u root -p$MYSQL_PASSWORD -e "SHOW BINARY LOGS;" | tail -n +2 | head -n 1 | awk '{print $1}' ) for binlog in $(mysql -u root -p$MYSQL_PASSWORD -e "SHOW BINARY LOGS;" | tail -n +2 | awk '{print $1}' | grep -v "$LATEST_BINLOG " ); do if [ ! -f "$BACKUP_DIR /$binlog .gz" ]; then echo "Backing up $binlog ..." gzip -c /var/log/mysql/$binlog > $BACKUP_DIR /$binlog .gz fi done find $BACKUP_DIR -name "*.gz" -mtime +$RETENTION_DAYS -delete echo "Binlog backup completed at $(date) "
5.4 PITR 恢复(Point-in-Time Recovery) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 mysql -u root -p < /backup/mysql/full-20260310.sql mysqlbinlog /backup/mysql/binlog/mysql-bin.000001.gz | mysql -u root -p mysqlbinlog /backup/mysql/binlog/mysql-bin.000002.gz | mysql -u root -p mysqlbinlog --stop-datetime="2026-03-12 10:59:00" \ /var/log/mysql/mysql-bin.000003 | mysql -u root -p mysqlbinlog --start-position=107 --stop-position=928 \ /var/log/mysql/mysql-bin.000003 | mysql -u root -p
六、自动化备份脚本 6.1 完整备份脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 #!/bin/bash set -eMYSQL_USER="backup" MYSQL_PASSWORD="your_secure_password" MYSQL_HOST="localhost" BACKUP_DIR="/backup/mysql" RETENTION_DAYS=30 LOG_FILE="/var/log/mysql-backup.log" DATE=$(date +%Y%m%d_%H%M%S) DAY_OF_WEEK=$(date +%u) log () { echo "[$(date '+%Y-%m-%d %H:%M:%S') ] $1 " | tee -a $LOG_FILE } error_exit () { log "ERROR: $1 " exit 1 } mkdir -p $BACKUP_DIR /{full,incremental,binlog}log "Starting MySQL backup..." if [ $DAY_OF_WEEK -eq 7 ]; then log "Performing full backup..." BACKUP_TYPE="full" BACKUP_PATH="$BACKUP_DIR /full/full-$DATE " mysqldump -u$MYSQL_USER -p$MYSQL_PASSWORD -h$MYSQL_HOST \ --single-transaction \ --quick \ --lock-tables=false \ --routines \ --triggers \ --events \ --all-databases | gzip > $BACKUP_PATH .sql.gz if [ $? -eq 0 ]; then log "Full backup completed: $BACKUP_PATH .sql.gz" BACKUP_SIZE=$(du -h $BACKUP_PATH .sql.gz | cut -f1) log "Backup size: $BACKUP_SIZE " else error_exit "Full backup failed" fi else log "Performing binlog backup..." BACKUP_TYPE="binlog" mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -h$MYSQL_HOST -e "FLUSH BINARY LOGS;" LATEST_BINLOG=$(mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -h$MYSQL_HOST \ -e "SHOW BINARY LOGS;" | tail -n +2 | head -n 1 | awk '{print $1}' ) for binlog in $(mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -h$MYSQL_HOST \ -e "SHOW BINARY LOGS;" | tail -n +2 | awk '{print $1}' | grep -v "$LATEST_BINLOG " ); do if [ ! -f "$BACKUP_DIR /binlog/$binlog .gz" ]; then gzip -c /var/log/mysql/$binlog > $BACKUP_DIR /binlog/$binlog .gz log "Backed up $binlog " fi done fi log "Cleaning up backups older than $RETENTION_DAYS days..." find $BACKUP_DIR /full -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete find $BACKUP_DIR /incremental -type d -mtime +$RETENTION_DAYS -exec rm -rf {} \; find $BACKUP_DIR /binlog -name "*.gz" -mtime +$RETENTION_DAYS -delete log "Verifying backup..." if [ $BACKUP_TYPE = "full" ]; then if gzip -t $BACKUP_PATH .sql.gz 2>/dev/null; then log "Backup verification passed" else error_exit "Backup verification failed" fi fi log "MySQL backup completed successfully" exit 0
6.2 恢复脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #!/bin/bash set -eMYSQL_USER="root" MYSQL_PASSWORD="your_password" BACKUP_DIR="/backup/mysql" echo "Available backups:" ls -lh $BACKUP_DIR /full/*.sql.gzread -p "Enter backup file to restore: " BACKUP_FILEif [ ! -f "$BACKUP_FILE " ]; then echo "Backup file not found!" exit 1 fi read -p "WARNING: This will overwrite existing data. Continue? (yes/no): " CONFIRMif [ "$CONFIRM " != "yes" ]; then echo "Restore cancelled" exit 0 fi echo "Stopping MySQL..." systemctl stop mysql echo "Clearing data directory..." rm -rf /var/lib/mysql/*echo "Restoring from $BACKUP_FILE ..." gunzip -c $BACKUP_FILE | mysql -u$MYSQL_USER -p$MYSQL_PASSWORD if [ $? -eq 0 ]; then echo "Data restored successfully" else echo "Restore failed!" exit 1 fi echo "Starting MySQL..." systemctl start mysql echo "Verifying restoration..." mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e "SHOW DATABASES;" echo "Restore completed successfully"
6.3 配置定时任务 1 2 3 4 5 6 7 8 9 crontab -e 0 1 * * * /usr/local/bin/mysql-backup.sh >> /var/log/mysql-backup.log 2>&1 0 1 * * 0 /usr/local/bin/mysql-backup.sh >> /var/log/mysql-backup.log 2>&1
七、主从复制备份 7.1 配置主从复制 主库配置 :
1 2 3 4 5 6 7 8 9 10 11 12 [mysqld] server-id = 1 log_bin = /var/log/mysql/mysql-bin.logbinlog_format = ROWgtid_mode = ON enforce_gtid_consistency = ON CREATE USER 'repl'@'%' IDENTIFIED BY 'secure_password' GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%' FLUSH PRIVILEGES
从库配置 :
1 2 3 4 5 6 7 8 [mysqld] server-id = 2 log_bin = /var/log/mysql/mysql-bin.logbinlog_format = ROWgtid_mode = ON enforce_gtid_consistency = ON read_only = ON
配置复制 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 mysql -u root -p -e "SHOW MASTER STATUS;" mysql -u root -p -e " CHANGE MASTER TO MASTER_HOST='master_ip', MASTER_USER='repl', MASTER_PASSWORD='secure_password', MASTER_AUTO_POSITION=1; " mysql -u root -p -e "START SLAVE;" mysql -u root -p -e "SHOW SLAVE STATUS\G"
7.2 从库备份优势 1 2 3 4 5 6 7 8 9 graph TB A[主库] -->|复制 | B[从库 1] A -->|复制 | C[从库 2] B --> D[备份任务] C --> E[查询负载] D --> F[不影响主库性能] E --> G[读写分离]
在从库执行备份 :
1 2 3 4 5 ssh slave-server "mysqldump -u root -p --all-databases > backup.sql" ssh slave-server "xtrabackup --backup --target-dir=/backup"
八、云环境备份 8.1 AWS RDS 备份 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 aws rds create-db-snapshot \ --db-instance-identifier mydb \ --db-snapshot-identifier mydb-snapshot-20260312 aws rds describe-db-snapshots \ --db-instance-identifier mydb aws rds restore-db-instance-from-db-snapshot \ --db-instance-identifier mydb-restored \ --db-snapshot-identifier mydb-snapshot-20260312 aws rds modify-db-instance \ --db-instance-identifier mydb \ --backup-retention-period 7 \ --preferred-backup-window 01:00-02:00
8.2 阿里云 RDS 备份 1 2 3 4 5 6 7 8 9 10 11 12 13 14 aliyun rds CreateBackup \ --DBInstanceId rm-xxx \ --BackupStrategy Manual aliyun rds DescribeBackups \ --DBInstanceId rm-xxx aliyun rds CloneDBInstance \ --DBInstanceId rm-xxx \ --BackupId 12345 \ --DBInstanceClass mysql.n2.medium.2
九、备份验证与演练 9.1 自动化验证脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #!/bin/bash set -eBACKUP_FILE=$1 TEST_DB="backup_verify_test_$$" if [ -z "$BACKUP_FILE " ]; then echo "Usage: $0 <backup_file>" exit 1 fi echo "Verifying backup: $BACKUP_FILE " mysql -u root -p -e "CREATE DATABASE $TEST_DB ;" gunzip -c $BACKUP_FILE | mysql -u root -p $TEST_DB if [ $? -ne 0 ]; then echo "Restore failed!" mysql -u root -p -e "DROP DATABASE $TEST_DB ;" exit 1 fi TABLE_COUNT=$(mysql -u root -p -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$TEST_DB ';" $TEST_DB ) echo "Tables restored: $TABLE_COUNT " RECORD_COUNT=$(mysql -u root -p -N -e "SELECT SUM(table_rows) FROM information_schema.tables WHERE table_schema='$TEST_DB ';" $TEST_DB ) echo "Records restored: $RECORD_COUNT " mysql -u root -p -e "SELECT COUNT(*) FROM $TEST_DB .users;" > /dev/null mysql -u root -p -e "SELECT COUNT(*) FROM $TEST_DB .orders;" > /dev/null mysql -u root -p -e "DROP DATABASE $TEST_DB ;" echo "Backup verification passed!"
9.2 恢复演练计划 1 2 3 4 5 6 7 8 9 10 11 gantt title 季度恢复演练计划 dateFormat YYYY-MM-DD section Q1 1 月演练 :done, 2026-01-15, 1d 2 月演练 :done, 2026-02-15, 1d 3 月演练 :active, 2026-03-15, 1d section Q2 4 月演练 :2026-04-15, 1d 5 月演练 :2026-05-15, 1d 6 月演练 :2026-06-15, 1d
演练步骤 :
选择最近的备份文件
在测试环境恢复
验证数据完整性
记录恢复时间(RTO)
记录数据丢失量(RPO)
编写演练报告
优化备份策略
十、监控与告警 10.1 备份监控脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #!/bin/bash BACKUP_DIR="/backup/mysql" MAX_AGE_HOURS=26 SLACK_WEBHOOK="https://hooks.slack.com/services/XXX" LATEST_BACKUP=$(find $BACKUP_DIR -name "*.sql.gz" -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -d' ' -f2-) if [ -z "$LATEST_BACKUP " ]; then MESSAGE="🚨 CRITICAL: No MySQL backup found!" curl -X POST -H 'Content-type: application/json' \ --data "{\"text\":\"$MESSAGE \"}" \ $SLACK_WEBHOOK exit 1 fi BACKUP_TIME=$(stat -c %Y $LATEST_BACKUP ) CURRENT_TIME=$(date +%s) AGE_HOURS=$(( (CURRENT_TIME - BACKUP_TIME) / 3600 )) if [ $AGE_HOURS -gt $MAX_AGE_HOURS ]; then MESSAGE="⚠️ WARNING: MySQL backup is $AGE_HOURS hours old (last: $LATEST_BACKUP )" curl -X POST -H 'Content-type: application/json' \ --data "{\"text\":\"$MESSAGE \"}" \ $SLACK_WEBHOOK exit 1 fi BACKUP_SIZE=$(du -m $LATEST_BACKUP | cut -f1) if [ $BACKUP_SIZE -lt 10 ]; then MESSAGE="⚠️ WARNING: MySQL backup is suspiciously small (${BACKUP_SIZE} MB)" curl -X POST -H 'Content-type: application/json' \ --data "{\"text\":\"$MESSAGE \"}" \ $SLACK_WEBHOOK exit 1 fi echo "✅ MySQL backup is healthy (age: ${AGE_HOURS} h, size: ${BACKUP_SIZE} MB)" exit 0
10.2 Prometheus 监控指标 1 2 3 4 5 6 7 8 scrape_configs: - job_name: 'mysql-backup' static_configs: - targets: ['localhost:9104' ] metrics_path: /probe params: module: [mysql_backup ]
1 2 3 4 5 mysql_backup_age_seconds mysql_backup_size_bytes mysql_backup_status mysql_binlog_age_seconds
十一、总结 备份策略检查清单
RTO/RPO 目标
数据级别
RPO(数据丢失)
RTO(恢复时间)
备份方案
核心数据
< 5 分钟
< 30 分钟
主从 + binlog
重要数据
< 1 小时
< 2 小时
每日增量 + binlog
一般数据
< 24 小时
< 4 小时
每日全量
最后更新 : 2026-03-12
标签 : #MySQL #备份 #恢复 #数据库运维 #数据安全
分类 : 数据库/运维
参考资料 :