Skip to content

MySQL 3-2-1 备份原则

3-2-1 备份原则概述

3-2-1备份原则是行业公认的最佳备份实践,为数据安全提供了可靠的保障:

  • 3个备份副本:至少保留3份完整的数据副本
  • 2种不同存储介质:使用2种不同类型的存储介质存储备份
  • 1个异地备份:至少1份备份存储在异地环境

版本差异考虑

MySQL 版本3-2-1 备份实现差异
MySQL 5.6支持XtraBackup 2.3+,不支持增量备份压缩,二进制日志管理功能有限
MySQL 5.7支持XtraBackup 2.4+,支持增量备份压缩,增强了二进制日志管理,支持GTID备份
MySQL 8.0支持XtraBackup 8.0+,支持克隆插件,增强了数据字典,支持增量备份的并行应用,支持redo日志归档

MySQL 环境下的 3-2-1 原则实现

实现3个备份副本

主备份 - 全量备份

实现方式

bash
# 使用 xtrabackup 进行全量备份(支持 MySQL 5.6+)
xtrabackup --backup --target-dir=/backup/full/$(date +%Y%m%d_%H%M) --user=backup --password=backup_password --parallel=4

# MySQL 5.7+ 支持压缩备份
xtrabackup --backup --compress --target-dir=/backup/full/$(date +%Y%m%d_%H%M) --user=backup --password=backup_password --parallel=4

# MySQL 8.0 支持并行压缩
xtrabackup --backup --compress --compress-threads=4 --target-dir=/backup/full/$(date +%Y%m%d_%H%M) --user=backup --password=backup_password --parallel=4

# 使用 mysqldump 进行逻辑全量备份(所有版本通用)
mysqldump --single-transaction --routines --triggers --events --all-databases --user=backup --password=backup_password > /backup/logical/$(date +%Y%m%d_%H%M)_full.sql

# 压缩逻辑备份
mysqldump --single-transaction --routines --triggers --events --all-databases --user=backup --password=backup_password | gzip > /backup/logical/$(date +%Y%m%d_%H%M)_full.sql.gz

增量备份

实现方式

bash
# 使用 xtrabackup 进行增量备份(MySQL 5.6+)
xtrabackup --backup --target-dir=/backup/inc/$(date +%Y%m%d_%H%M) --incremental-basedir=/backup/full/$(date +%Y%m%d_0200) --user=backup --password=backup_password --parallel=4

# MySQL 5.7+ 支持压缩增量备份
xtrabackup --backup --compress --target-dir=/backup/inc/$(date +%Y%m%d_%H%M) --incremental-basedir=/backup/full/$(date +%Y%m%d_0200) --user=backup --password=backup_password --parallel=4

# MySQL 8.0 支持并行压缩增量备份
xtrabackup --backup --compress --compress-threads=4 --target-dir=/backup/inc/$(date +%Y%m%d_%H%M) --incremental-basedir=/backup/full/$(date +%Y%m%d_0200) --user=backup --password=backup_password --parallel=4

日志备份

实现方式

bash
# 启用 binlog(所有版本通用)
# my.cnf 配置
log_bin = /var/lib/mysql/binlog
max_binlog_size = 100M
binlog_format = ROW

# MySQL 5.7+ 增强配置
binlog_row_image = FULL
expire_logs_days = 7  # MySQL 5.6-5.7
binlog_expire_logs_seconds = 604800  # MySQL 5.7+ 推荐使用

# 定时归档 binlog
# 创建归档目录
mkdir -p /backup/binlog/$(date +%Y%m%d)

# 复制 binlog 到归档目录
rsync -avz --delete /var/lib/mysql/binlog.* /backup/binlog/$(date +%Y%m%d)/ --exclude='binlog.index'

# 清理过期 binlog(MySQL 5.7+ 使用 binlog_expire_logs_seconds 自动清理)
mysql -u root -p -e "PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 7 DAY)"

使用2种不同存储介质

本地存储

推荐选项

  • SSD 存储:用于频繁访问的备份,如最近7天的全量和增量备份
  • NAS 存储:用于中期备份存储,如最近30天的备份

实现方式

bash
# 挂载 NAS 存储
mount -t nfs nas-server:/backup /mnt/nas-backup

# 复制备份到 NAS
rsync -avz --delete-delay /backup/full/$(date +%Y%m%d_0200) /mnt/nas-backup/full/
rsync -avz --delete-delay /backup/inc/$(date +%Y%m%d)_* /mnt/nas-backup/inc/

对象存储

推荐选项

  • AWS S3
  • 阿里云 OSS
  • 腾讯云 COS

实现方式

bash
# 使用 s3cmd 同步备份到 S3
s3cmd sync /backup/full/$(date +%Y%m%d_0200) s3://mysql-backup-bucket/full/

# 使用 rclone 同步到阿里云 OSS
rclone sync /backup/full/$(date +%Y%m%d_0200) oss:mysql-backup-bucket/full/

# 同步增量备份
rclone sync /backup/inc/$(date +%Y%m%d)_* oss:mysql-backup-bucket/inc/

实现1个异地备份

异地数据中心

实现方式

bash
# 使用 rsync 同步到异地数据中心
rsync -avz --delete --bwlimit=10000 --timeout=3600 /backup/ user@remote-dc:/backup/

云存储

实现方式

bash
# 使用 rclone 同步到异地云存储(不同区域)
rclone sync /backup/ remote-oss:mysql-backup-bucket-dr/

3-2-1 原则的实际应用案例

小型企业场景

环境

  • 单台 MySQL 5.7 服务器
  • 数据量:100GB
  • RTO/RPO:4小时/1小时

实现方案

  • 每日凌晨2点执行全量备份(本地SSD)
  • 每6小时执行增量备份(本地SSD)
  • 每日同步到本地NAS存储
  • 每日同步到阿里云OSS(异地)

中型企业场景

环境

  • MySQL 5.7 主从复制架构
  • 数据量:500GB
  • RTO/RPO:1小时/15分钟

实现方案

  • 主库:每2小时执行增量备份
  • 从库:每日凌晨2点执行全量备份(本地SSD)
  • 每4小时同步到本地NAS存储
  • 每8小时同步到异地数据中心
  • 每日同步到AWS S3

大型企业场景

环境

  • MySQL 8.0 MGR 集群架构
  • 数据量:5TB
  • RTO/RPO:30分钟/5分钟

实现方案

  • 每小时执行增量备份
  • 每周日凌晨2点执行全量备份
  • 本地存储:NVMe SSD
  • 二级存储:企业级NAS
  • 异地存储:
    • 同城灾备中心(延迟<5分钟)
    • 异地灾备中心(延迟<30分钟)
    • 云存储归档(AWS Glacier)

3-2-1 备份策略的监控与验证

备份监控

实现方式

bash
# 监控备份是否成功
# 检查备份目录大小
du -sh /backup/full/$(date +%Y%m%d_0200) > /var/log/backup/size_$(date +%Y%m%d).log

# 检查备份日志
grep -i "completed OK" /var/log/backup/xtrabackup_$(date +%Y%m%d).log || echo "Backup failed" > /var/log/backup/status_$(date +%Y%m%d).log

# 发送备份状态告警(示例)
if grep -q "failed" /var/log/backup/status_$(date +%Y%m%d).log; then
    curl -X POST -H "Content-Type: application/json" -d '{"msg": "MySQL备份失败,请检查"}' https://your-alert-system.com/api/alerts
fi

备份验证

实现方式

bash
# 验证 xtrabackup 备份完整性
xtrabackup --prepare --target-dir=/backup/full/$(date +%Y%m%d_0200)

# 验证 mysqldump 备份完整性
mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS test_restore;" 
mysql -u root -p test_restore < /backup/logical/$(date +%Y%m%d_0200)_full.sql

# 定期恢复测试(每月一次)
# 恢复到测试环境
mkdir -p /test/mysql/data
chown mysql:mysql /test/mysql/data
xtrabackup --copy-back --target-dir=/backup/full/$(date +%Y%m%d_0200) --datadir=/test/mysql/data
chown -R mysql:mysql /test/mysql/data

# 启动测试实例
mysqld --datadir=/test/mysql/data --socket=/tmp/test_mysql.sock --port=3308 --skip-networking &
sleep 10

# 验证恢复结果
mysql --socket=/tmp/test_mysql.sock -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys');"

# 停止测试实例
pkill -f "mysqld --datadir=/test/mysql/data"

3-2-1 备份原则的最佳实践

备份周期规划

备份类型备份频率保留期限存储介质
全量备份每日/每周30天本地SSD + NAS
增量备份每小时/每6小时7天本地SSD
Binlog备份实时15天本地SSD + NAS + 异地
逻辑备份每周90天对象存储

存储介质选择

存储类型优势劣势适用场景
SSD速度快,可靠性高成本高近期备份,频繁访问
HDD成本低,容量大速度慢中期备份
NAS易于管理,可共享依赖网络团队协作,中期备份
对象存储成本低,容量无限访问延迟高长期归档,异地备份

异地备份策略

异地类型距离RTO成本适用场景
同城异地<50km<1小时中高核心业务,高可用性要求
跨城异地50-500km<4小时重要业务,中等可用性要求
跨省异地>500km<24小时一般业务,灾难恢复要求

3-2-1 备份原则的自动化实现

使用 Shell 脚本自动化

实现方式

bash
#!/bin/bash
# MySQL 3-2-1 备份脚本
# 支持版本:MySQL 5.6/5.7/8.0
# 功能:自动执行全量备份、增量备份和binlog备份,同步到NAS和异地

# 配置参数
BACKUP_DIR="/backup"
NAS_DIR="/mnt/nas-backup"
REMOTE_DIR="user@remote-dc:/backup"
S3_BUCKET="s3://mysql-backup-bucket"
DB_USER="backup"
DB_PASS="backup_password"
LOG_DIR="/var/log/backup"
DATE=$(date +%Y%m%d)
TIME=$(date +%H%M)
FULL_BACKUP_TAG="${DATE}_0200"

# 错误处理函数
error_exit() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1" >> "${LOG_DIR}/backup_${DATE}.log"
    curl -X POST -H "Content-Type: application/json" -d '{"msg": "MySQL备份失败: '$1'"}' https://your-alert-system.com/api/alerts
    exit 1
}

# 日志函数
log() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" >> "${LOG_DIR}/backup_${DATE}.log"
}

# 创建备份目录
mkdir -p "${BACKUP_DIR}/full"
mkdir -p "${BACKUP_DIR}/inc"
mkdir -p "${BACKUP_DIR}/binlog/${DATE}"
mkdir -p "${LOG_DIR}"

# 1. 执行全量备份(每日凌晨2点执行)
if [ "$TIME" = "0200" ]; then
    log "开始全量备份..."
    
    # 获取MySQL版本
    MYSQL_VERSION=$(mysql -u${DB_USER} -p${DB_PASS} -e "SELECT VERSION();" -ss | grep -o '^[0-9]\+\.[0-9]\+')
    
    if (( $(echo "$MYSQL_VERSION >= 8.0" | bc -l) )); then
        # MySQL 8.0 备份
        xtrabackup --backup --compress --compress-threads=4 --target-dir="${BACKUP_DIR}/full/${FULL_BACKUP_TAG}" --user=${DB_USER} --password=${DB_PASS} --parallel=4 2>> "${LOG_DIR}/backup_${DATE}.log"
    elif (( $(echo "$MYSQL_VERSION >= 5.7" | bc -l) )); then
        # MySQL 5.7 备份
        xtrabackup --backup --compress --target-dir="${BACKUP_DIR}/full/${FULL_BACKUP_TAG}" --user=${DB_USER} --password=${DB_PASS} --parallel=4 2>> "${LOG_DIR}/backup_${DATE}.log"
    else
        # MySQL 5.6 备份
        xtrabackup --backup --target-dir="${BACKUP_DIR}/full/${FULL_BACKUP_TAG}" --user=${DB_USER} --password=${DB_PASS} --parallel=4 2>> "${LOG_DIR}/backup_${DATE}.log"
    fi
    
    if [ $? -ne 0 ]; then
        error_exit "全量备份失败"
    fi
    
    log "全量备份完成: ${BACKUP_DIR}/full/${FULL_BACKUP_TAG}"
    
    # 复制到 NAS
    log "同步到 NAS..."
    rsync -avz --delete-delay "${BACKUP_DIR}/full/${FULL_BACKUP_TAG}" "${NAS_DIR}/full/" >> "${LOG_DIR}/backup_${DATE}.log" 2>&1
    
    # 复制到异地
    log "同步到异地数据中心..."
    rsync -avz --delete --bwlimit=10000 "${BACKUP_DIR}/full/${FULL_BACKUP_TAG}" "${REMOTE_DIR}/full/" >> "${LOG_DIR}/backup_${DATE}.log" 2>&1
    
    # 复制到 S3
    log "同步到 S3..."
    s3cmd sync "${BACKUP_DIR}/full/${FULL_BACKUP_TAG}" "${S3_BUCKET}/full/" >> "${LOG_DIR}/backup_${DATE}.log" 2>&1
fi

# 2. 执行增量备份(每6小时执行)
if [[ "$TIME" =~ ^(0800|1400|2000)$ ]]; then
    log "开始增量备份..."
    
    # 检查全量备份是否存在
    if [ ! -d "${BACKUP_DIR}/full/${FULL_BACKUP_TAG}" ]; then
        error_exit "全量备份不存在,无法执行增量备份"
    fi
    
    # 获取MySQL版本
    MYSQL_VERSION=$(mysql -u${DB_USER} -p${DB_PASS} -e "SELECT VERSION();" -ss | grep -o '^[0-9]\+\.[0-9]\+')
    
    if (( $(echo "$MYSQL_VERSION >= 8.0" | bc -l) )); then
        # MySQL 8.0 增量备份
        xtrabackup --backup --compress --compress-threads=4 --target-dir="${BACKUP_DIR}/inc/${DATE}_${TIME}" --incremental-basedir="${BACKUP_DIR}/full/${FULL_BACKUP_TAG}" --user=${DB_USER} --password=${DB_PASS} --parallel=4 2>> "${LOG_DIR}/backup_${DATE}.log"
    elif (( $(echo "$MYSQL_VERSION >= 5.7" | bc -l) )); then
        # MySQL 5.7 增量备份
        xtrabackup --backup --compress --target-dir="${BACKUP_DIR}/inc/${DATE}_${TIME}" --incremental-basedir="${BACKUP_DIR}/full/${FULL_BACKUP_TAG}" --user=${DB_USER} --password=${DB_PASS} --parallel=4 2>> "${LOG_DIR}/backup_${DATE}.log"
    else
        # MySQL 5.6 增量备份
        xtrabackup --backup --target-dir="${BACKUP_DIR}/inc/${DATE}_${TIME}" --incremental-basedir="${BACKUP_DIR}/full/${FULL_BACKUP_TAG}" --user=${DB_USER} --password=${DB_PASS} --parallel=4 2>> "${LOG_DIR}/backup_${DATE}.log"
    fi
    
    if [ $? -ne 0 ]; then
        error_exit "增量备份失败"
    fi
    
    log "增量备份完成: ${BACKUP_DIR}/inc/${DATE}_${TIME}"
    
    # 复制到 NAS
    rsync -avz --delete-delay "${BACKUP_DIR}/inc/${DATE}_${TIME}" "${NAS_DIR}/inc/" >> "${LOG_DIR}/backup_${DATE}.log" 2>&1
fi

# 3. 备份 Binlog(每小时执行)
log "开始备份 Binlog..."
mysql -u${DB_USER} -p${DB_PASS} -e "FLUSH LOGS" 2>> "${LOG_DIR}/backup_${DATE}.log"
rsync -avz /var/lib/mysql/binlog.* "${BACKUP_DIR}/binlog/${DATE}/" --exclude='binlog.index' 2>> "${LOG_DIR}/backup_${DATE}.log"
rsync -avz "${BACKUP_DIR}/binlog/${DATE}/" "${NAS_DIR}/binlog/${DATE}/" 2>> "${LOG_DIR}/backup_${DATE}.log"
rsync -avz "${BACKUP_DIR}/binlog/${DATE}/" "${REMOTE_DIR}/binlog/${DATE}/" 2>> "${LOG_DIR}/backup_${DATE}.log"

log "Binlog备份完成"
log "备份任务执行完成"

使用 Ansible 自动化

实现方式

yaml
# ansible-playbook mysql_backup.yml
- name: MySQL 3-2-1 Backup
  hosts: mysql_servers
  become: yes
  vars:
    backup_dir: /backup
    nas_dir: /mnt/nas-backup
    remote_dir: user@remote-dc:/backup
    s3_bucket: s3://mysql-backup-bucket
    db_user: backup
    db_pass: backup_password
    log_dir: /var/log/backup
  tasks:
    - name: 创建备份目录
      file:
        path: "{{ item }}"
        state: directory
        mode: 0755
      loop:
        - "{{ backup_dir }}/full"
        - "{{ backup_dir }}/inc"
        - "{{ backup_dir }}/binlog/{{ ansible_date_time.date }}"
        - "{{ log_dir }}"

    - name: 获取MySQL版本
      command: mysql -u{{ db_user }} -p{{ db_pass }} -e "SELECT VERSION();"
      register: mysql_version_output
      changed_when: false

    - name: 设置MySQL主版本
      set_fact:
        mysql_major_version: "{{ mysql_version_output.stdout | regex_search('^[0-9]+\.[0-9]+') }}"

    - name: 执行全量备份(MySQL 8.0)
      command: >
        xtrabackup --backup --compress --compress-threads=4 
        --target-dir={{ backup_dir }}/full/{{ ansible_date_time.date }}_0200
        --user={{ db_user }} --password={{ db_pass }} --parallel=4
      when:
        - ansible_date_time.hour == "02"
        - mysql_major_version is version('8.0', '>=')
      register: full_backup_result

    - name: 执行全量备份(MySQL 5.7)
      command: >
        xtrabackup --backup --compress 
        --target-dir={{ backup_dir }}/full/{{ ansible_date_time.date }}_0200
        --user={{ db_user }} --password={{ db_pass }} --parallel=4
      when:
        - ansible_date_time.hour == "02"
        - mysql_major_version is version('5.7', '>=') and mysql_major_version is version('8.0', '<')
      register: full_backup_result

    - name: 执行全量备份(MySQL 5.6)
      command: >
        xtrabackup --backup 
        --target-dir={{ backup_dir }}/full/{{ ansible_date_time.date }}_0200
        --user={{ db_user }} --password={{ db_pass }} --parallel=4
      when:
        - ansible_date_time.hour == "02"
        - mysql_major_version is version('5.6', '>=') and mysql_major_version is version('5.7', '<')
      register: full_backup_result

    - name: 同步到 NAS
      synchronize:
        src: "{{ backup_dir }}/full/{{ ansible_date_time.date }}_0200"
        dest: "{{ nas_dir }}/full/"
      when: ansible_date_time.hour == "02"

    - name: 同步到异地
      synchronize:
        src: "{{ backup_dir }}/full/{{ ansible_date_time.date }}_0200"
        dest: "{{ remote_dir }}/full/"
        mode: push
      when: ansible_date_time.hour == "02"

3-2-1 备份原则的成本优化

存储成本优化

实现方式

  • 使用压缩技术:xtrabackup --compressmysqldump | gzip
  • 采用分层存储:热数据存储在SSD,冷数据存储在对象存储
  • 定期清理过期备份:使用 find /backup/full -type d -mtime +30 -delete
  • 增量备份策略:减少全量备份频率,增加增量备份频率

网络成本优化

实现方式

  • 选择合适的同步时间:避开业务高峰期
  • 使用增量同步:只同步变化的数据
  • 限制同步带宽:使用 rsync --bwlimit
  • 压缩传输数据:使用 rsync -zssh -C

管理成本优化

实现方式

  • 自动化备份和验证流程
  • 集中化监控和告警
  • 标准化备份策略和文档
  • 定期演练和培训

总结

3-2-1备份原则是MySQL数据安全的基石,通过实现3个备份副本、2种不同存储介质和1个异地备份,可以有效应对各种数据丢失场景,包括硬件故障、自然灾害和人为错误。

在实际运维中,DBA需要根据业务需求、数据量大小和成本预算,灵活调整3-2-1备份策略,确保数据安全的同时,优化备份成本和管理复杂度。

通过自动化实现和定期验证,可以确保3-2-1备份原则的有效执行,为MySQL数据库提供可靠的数据安全保障。不同MySQL版本在备份实现上存在差异,DBA需要根据实际版本选择合适的备份工具和参数配置。