一文详解MySQL备份与恢复基础流程

描述

背景

数据备份是数据库运维的最后一道防线。无论系统设计多么健壮、人为操作多么谨慎,硬件故障、软件 BUG、人为误删都可能在毫无预兆的情况下发生。没有可用的备份,意味着业务数据面临永久丢失的风险。2024 年国内某云厂商因备份系统缺陷导致客户数据无法恢复的案例,至今仍是运维圈的反面教材。

MySQL 8.0 的备份体系相比 5.7 时代有了显著进步。MySQL Shell 8.0(原名 MySQL Shell)提供了全新的备份恢复工具集,xtrabackup 也已更新到 8.0.35 版本,支持 MySQL 8.0 的所有新特性。2026 年,主流备份方案已从单一的物理备份或逻辑备份演变为“分层备份策略”——日常增量备份 + 定期全量备份 + 异地容灾。

本文以 MySQL 8.0.39 为基准,系统讲解全量备份、增量备份的原理与实践,覆盖 mysqldump、xtrabackup、mydumper 三种主流工具,提供可直接落地的脚本和完整的恢复演练流程。

前置知识要求: 了解 MySQL 存储引擎(InnoDB/MyISAM)、熟悉 Linux 基础命令、理解 binlog 原理。

1. 备份的重要性与风险评估

1.1 备份的分级与 RPO/RTO

在讨论备份方案之前,运维必须明确两个关键指标:

RPO(Recovery Point Objective):可接受的最大数据丢失量,通常以时间衡量。比如 RPO = 1小时,意味着最多允许丢失1小时的数据。

RTO(Recovery Time Objective):系统恢复所需的最大时间。比如 RTO = 4小时,意味着故障后4小时内必须恢复服务。

不同业务场景的 RPO/RTO 要求:

业务场景 RPO RTO
核心交易系统 < 5分钟 < 30分钟
用户数据(账户、订单) < 1小时 < 4小时
日志类数据 < 24小时 < 24小时
归档历史数据 < 1周 < 1周

1.2 常见数据丢失场景

 

# 人为误操作风险评估清单
# 场景1: 误删除表
# 风险等级: 极高
# 恢复难度: 高(需要从备份恢复 + binlog 补数据)

# 场景2: 恶意truncate表
# 风险等级: 极高
# 恢复难度: 高(binlog 必须包含原始数据)

# 场景3: DROP DATABASE
# 风险等级: 灾难级
# 恢复难度: 极高(整个数据库需要重建)

# 场景4: 升级失败导致数据目录损坏
# 风险等级: 高
# 恢复难度: 中(如果有物理备份,恢复较快)

# 场景5: 主从切换后从库数据不一致
# 风险等级: 中
# 恢复难度: 低(重新从主库同步)

 

1.3 备份策略选择决策树

 

业务对数据丢失的容忍度?
    |
    +-- 极低(RPO < 5分钟)
    |   |
    |   +-- 方案: xtrabackup增量备份(每5-15分钟) + binlog实时备份
    |
    +-- 低(RPO 1小时以内)
    |   |
    |   +-- 方案: xtrabackup增量备份(每小时) + 每日全量
    |
    +-- 中等(RPO 1-24小时)
    |   |
    |   +-- 方案: 每日mysqldump全量 + binlog归档
    |
    +-- 宽松(RPO > 24小时)
        |
        +-- 方案: 每日mysqldump全量

 

2. 备份方法对比

2.1 逻辑备份 vs 物理备份

逻辑备份通过 SQL 语句导出数据,备份的是数据内容和表结构。工具包括 mysqldump、mydumper。

优点:跨 MySQL 版本使用、备份文件可读、恢复灵活

缺点:备份速度慢、恢复速度更慢、无法保证数据一致性(备份过程中数据仍在写入)

物理备份直接复制 MySQL 数据文件,备份的是数据库的物理文件。工具包括 xtrabackup、mysqlbackup。

优点:备份/恢复速度快、可以保持数据一致性(通过FTWRL或xtrabackup的内部机制)

缺点:备份文件大、只能恢复到相同版本的 MySQL、跨平台困难

2.2 主流工具对比

工具 类型 备份速度 恢复速度 数据一致性 增量备份 推荐场景
mysqldump 逻辑 需要 --single-transaction 不支持 < 10GB,小型业务
mydumper 逻辑 支持事务 不支持 中大型数据库
xtrabackup 物理 支持(自动处理) 支持 大型数据库,核心业务
mysqlbackup 物理 支持 支持 Oracle MySQL 企业版

2.3 mysqldump 详解

mysqldump 是 MySQL 自带的逻辑备份工具,几乎所有 MySQL 环境都可以直接使用。但它的使用有很多讲究,错误的用法会导致数据不一致或备份失败。

基础用法:

 

# 备份单个数据库
mysqldump -u root -p -h localhost mydb > /backup/mydb_$(date +%Y%m%d).sql

# 备份所有数据库
mysqldump -u root -p -h localhost --all-databases > /backup/all_$(date +%Y%m%d).sql

# 备份特定表
mysqldump -u root -p mydb users orders > /backup/mydb_tables_$(date +%Y%m%d).sql

# 压缩备份(节省空间)
mysqldump -u root -p mydb | gzip > /backup/mydb_$(date +%Y%m%d).sql.gz

 

一致性备份的关键参数:

 

# 使用事务保证 InnoDB 数据一致性(重要!)
mysqldump -u root -p 
    --single-transaction 
    --routines 
    --triggers 
    --events 
    --master-data=2 
    --flush-logs 
    mydb > /backup/mydb_$(date +%Y%m%d).sql

 

参数说明:

--single-transaction:对 InnoDB 表开启一个一致性快照事务,备份期间不影响业务读写

--routines:备份存储过程和函数

--triggers:备份触发器

--events:备份事件调度器事件

--master-data=2:在备份文件中记录 binlog 文件名和位置(用于从库搭建)

--flush-logs:备份前刷新日志,切割 binlog

警告:以下用法会导致数据不一致

 

# 错误示例:没有使用 --single-transaction
mysqldump -u root -p mydb > /backup/mydb.sql  # MyISAM 表可能出现不一致

# 错误示例:混合使用 --lock-tables 和 --single-transaction
mysqldump -u root -p --lock-tables --single-transaction mydb  # 两个参数互斥

 

2.4 xtrabackup 详解

xtrabackup 是 Percona 公司开发的物理备份工具,是 MySQL 大规模备份的首选方案。xtrabackup 8.0.35 支持 MySQL 8.0.39。

安装 xtrabackup:

 

#!/bin/bash
# install_xtrabackup.sh

# 方法1: 通过 Percona 仓库安装(推荐)
yum install -y https://repo.percona.com/yum/percona-release-latest.noarch.rpm
yum install -y percona-xtrabackup-80

# 方法2: 通过 APT 安装(Ubuntu/Debian)
wget -q https://repo.percona.com/apt/percona-release_latest.generic_all.deb
dpkg -i percona-release_latest.generic_all.deb
apt-get update
apt-get install -y percona-xtrabackup-80

 

全量备份:

 

#!/bin/bash
# xtrabackup_full_backup.sh
# MySQL 8.0 xtrabackup 全量备份脚本

BACKUP_DIR="/backup/xtrabackup"
MYSQL_USER="backup_user"
MYSQL_PASSWORD="BackupPass2026!"
MYSQL_SOCKET="/var/lib/mysql/mysql.sock"
RETENTION_DAYS=7

# 创建备份目录
mkdir -p "${BACKUP_DIR}/full"
mkdir -p "${BACKUP_DIR}/incr"

# 生成备份名称
BACKUP_NAME="full_$(date +%Y%m%d_%H%M%S)"
BACKUP_PATH="${BACKUP_DIR}/full/${BACKUP_NAME}"

echo"$(date '+%Y-%m-%d %H:%M:%S') - 开始全量备份..."

# 执行全量备份
xtrabackup 
    --user="${MYSQL_USER}" 
    --password="${MYSQL_PASSWORD}" 
    --socket="${MYSQL_SOCKET}" 
    --backup 
    --target-dir="${BACKUP_PATH}" 
    --datadir=/var/lib/mysql 
    --ftwrl-wait-timeout=300 
    --ftwrl-wait-threshold=200 
    --wait-for-mysql 
    --no-version-check 
    --parallel=4 
    --throttle=100

if [ $? -eq 0 ]; then
    echo"$(date '+%Y-%m-%d %H:%M:%S') - 全量备份成功: ${BACKUP_PATH}"

    # 准备备份(应用事务日志,使备份一致)
    echo"$(date '+%Y-%m-%d %H:%M:%S') - 开始准备备份..."
    xtrabackup 
        --prepare 
        --target-dir="${BACKUP_PATH}" 
        --no-version-check

    echo"$(date '+%Y-%m-%d %H:%M:%S') - 备份准备完成"

    # 清理过期备份
    find "${BACKUP_DIR}/full" -type d -mtime +${RETENTION_DAYS} -exec rm -rf {} ; 2>/dev/null
    echo"$(date '+%Y-%m-%d %H:%M:%S') - 已清理 ${RETENTION_DAYS} 天前的备份"
else
    echo"$(date '+%Y-%m-%d %H:%M:%S') - 全量备份失败!"
    exit 1
fi

 

增量备份:

 

#!/bin/bash
# xtrabackup_incr_backup.sh
# 基于上一次全量备份的增量备份脚本

BACKUP_DIR="/backup/xtrabackup"
MYSQL_USER="backup_user"
MYSQL_PASSWORD="BackupPass2026!"
MYSQL_SOCKET="/var/lib/mysql/mysql.sock"
BASE_BACKUP=""

# 获取最新的全量备份作为基准
BASE_BACKUP=$(ls -td ${BACKUP_DIR}/full/full_* 2>/dev/null | head -1)

if [ -z "$BASE_BACKUP" ]; then
    echo"未找到全量备份,请先执行全量备份"
    exit 1
fi

INCR_BACKUP="incr_$(date +%Y%m%d_%H%M%S)"
INCR_PATH="${BACKUP_DIR}/incr/${INCR_BACKUP}"

echo"$(date '+%Y-%m-%d %H:%M:%S') - 基于 ${BASE_BACKUP} 执行增量备份..."

xtrabackup 
    --user="${MYSQL_USER}" 
    --password="${MYSQL_PASSWORD}" 
    --socket="${MYSQL_SOCKET}" 
    --backup 
    --target-dir="${INCR_PATH}" 
    --incremental-basedir="${BASE_BACKUP}" 
    --datadir=/var/lib/mysql 
    --parallel=4

if [ $? -eq 0 ]; then
    echo"$(date '+%Y-%m-%d %H:%M:%S') - 增量备份成功: ${INCR_PATH}"
else
    echo"$(date '+%Y-%m-%d %H:%M:%S') - 增量备份失败!"
    exit 1
fi

 

2.5 mydumper 详解

mydumper 是 mysqldump 的多线程替代品,备份速度通常比 mysqldump 快 3-10 倍,尤其适合大型数据库。

安装 mydumper:

 

# Ubuntu/Debian
apt-get install -y mydumper

# CentOS/RHEL
yum install -y mydumper

# 从源码编译
git clone https://github.com/mydumper/mydumper.git
cd mydumper
cmake .
make -j$(nproc)
make install

 

基础用法:

 

# 备份单个数据库(多线程)
mydumper 
    -u root 
    -p 'MyPassword2026!' 
    -h localhost 
    -B mydb 
    -o /backup/mydb_$(date +%Y%m%d) 
    -t 8            # 8个线程
    -v 3             # verbose 模式

# 备份所有数据库
mydumper 
    -u root 
    -p 'MyPassword2026!' 
    -h localhost 
    -o /backup/all_$(date +%Y%m%d) 
    -t 8

 

恢复命令(myloader):

 

# 恢复数据库
myloader 
    -u root 
    -p 'MyPassword2026!' 
    -h localhost 
    -d /backup/mydb_20261015 
    -t 8 
    -B mydb

 

3. 全量备份脚本

3.1 定时全量备份脚本(支持加密压缩)

 

#!/bin/bash
# mysql_full_backup.sh
# MySQL 全量备份脚本 - 支持压缩、加密、异地传输
# 建议 cron: 0 2 * * * /opt/scripts/mysql_full_backup.sh

set -euo pipefail

# ==================== 配置区域 ====================
MYSQL_USER="backup_admin"
MYSQL_PASSWORD="SecureBackupPass2026!"
MYSQL_HOST="localhost"
MYSQL_SOCKET="/var/lib/mysql/mysql.sock"
BACKUP_ROOT="/backup/mysql"
RETENTION_DAYS=30
GZIP_LEVEL=6
ENCRYPT_KEY_FILE="/etc/backup/aes_keyfile"
REMOTE_BACKUP_HOST="backup-server.example.com"
REMOTE_BACKUP_USER="rsync_user"
REMOTE_BACKUP_PATH="/backup/mysql"
ENABLE_REMOTE_SYNC=false
ENABLE_ENCRYPT=false
# ==================== 配置结束 ====================

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

# 错误处理函数
error_exit() {
    log"ERROR: $1"
    exit 1
}

# 检查 MySQL 连接
check_mysql() {
    log"检查 MySQL 连接..."
    if ! mysql -u "${MYSQL_USER}" -p"${MYSQL_PASSWORD}" -h "${MYSQL_HOST}" -e "SELECT 1" >/dev/null 2>&1; then
        error_exit "MySQL 连接失败"
    fi
    log"MySQL 连接正常"
}

# 获取备份数据库列表
get_databases() {
    mysql -u "${MYSQL_USER}" -p"${MYSQL_PASSWORD}" -h "${MYSQL_HOST}" -N -e "SHOW DATABASES" 2>/dev/null | 
        grep -vE '(information_schema|performance_schema|mysql|sys|test)'
}

# 备份单个数据库
backup_database() {
    local db_name=$1
    local backup_file="${BACKUP_ROOT}/${BACKUP_DATE}/${db_name}.sql"

    log"备份数据库: ${db_name}"

    # 使用 --single-transaction 保证 InnoDB 一致性
    # 使用 --master-data=2 记录 binlog 位置
    mysqldump 
        --user="${MYSQL_USER}" 
        --password="${MYSQL_PASSWORD}" 
        --host="${MYSQL_HOST}" 
        --socket="${MYSQL_SOCKET}" 
        --single-transaction 
        --routines 
        --triggers 
        --events 
        --master-data=2 
        --flush-logs 
        --lock-tables=false 
        --add-drop-database 
        --databases "${db_name}" 2>/dev/null | 
        gzip -${GZIP_LEVEL} > "${backup_file}.gz"

    if [ $? -eq 0 ] && [ -s "${backup_file}.gz" ]; then
        log"数据库 ${db_name} 备份成功: $(du -h ${backup_file}.gz | cut -f1)"
    else
        error_exit "数据库 ${db_name} 备份失败"
    fi
}

# 备份所有表结构(不含数据)
backup_schema() {
    local db_name=$1
    local schema_file="${BACKUP_ROOT}/${BACKUP_DATE}/${db_name}_schema.sql"

    mysqldump 
        --user="${MYSQL_USER}" 
        --password="${MYSQL_PASSWORD}" 
        --host="${MYSQL_HOST}" 
        --socket="${MYSQL_SOCKET}" 
        --no-data 
        --routines 
        --triggers 
        --events 
        --databases "${db_name}" 2>/dev/null | 
        gzip -${GZIP_LEVEL} > "${schema_file}.gz"

    log"数据库 ${db_name} 表结构备份完成"
}

# 生成备份元信息
generate_metadata() {
    local metadata_file="${BACKUP_ROOT}/${BACKUP_DATE}/metadata.txt"

    {
        echo"Backup Date: ${BACKUP_DATE}"
        echo"Backup Time: $(date '+%Y-%m-%d %H:%M:%S')"
        echo"MySQL Version: $(mysql -u "${MYSQL_USER}" -p"${MYSQL_PASSWORD}" -h "${MYSQL_HOST}" -N -e "SELECT VERSION();" 2>/dev/null)"
        echo "Binlog Position: $(mysql -u "${MYSQL_USER}" -p"${MYSQL_PASSWORD}" -h "${MYSQL_HOST}" -N -e "SHOW MASTER STATUS;" 2>/dev/null | head -1)"
        echo "Databases: $(get_databases | tr '
'', ')"
    } > "${metadata_file}"

    log "元信息文件已生成"
}

# 加密备份文件
encrypt_backup() {
    if [ "$ENABLE_ENCRYPT" = true ] && [ -f "$ENCRYPT_KEY_FILE" ]; then
        log "加密备份文件..."
        local encrypt_key=$(cat "$ENCRYPT_KEY_FILE")
        for f in "${BACKUP_ROOT}/${BACKUP_DATE}"/*.gz; do
            openssl enc -aes-256-cbc -salt -pbkdf2 -in "$f" -out "${f}.enc" -pass pass:"$encrypt_key" 2>/dev/null && 
                rm -f "$f" && 
                mv "${f}.enc" "$f"
            log "已加密: $(basename $f)"
        done
    fi
}

# 清理过期备份
cleanup_old_backups() {
    log "清理 ${RETENTION_DAYS} 天前的备份..."
    find "${BACKUP_ROOT}" -type d -name "????-??-??" -mtime +${RETENTION_DAYS} -exec rm -rf {} ; 2>/dev/null
    log "过期备份清理完成"
}

# 同步到远程服务器
sync_to_remote() {
    if [ "$ENABLE_REMOTE_SYNC" = true ]; then
        log "同步备份到远程服务器..."
        rsync -avz --progress 
            -e "ssh -o StrictHostKeyChecking=no" 
            "${BACKUP_ROOT}/${BACKUP_DATE}/" 
            "${REMOTE_BACKUP_USER}@${REMOTE_BACKUP_HOST}:${REMOTE_BACKUP_PATH}/${BACKUP_DATE}/"
        log "远程同步完成"
    fi
}

# 验证备份完整性
verify_backup() {
    log "验证备份完整性..."
    local failed=0
    for f in "${BACKUP_ROOT}/${BACKUP_DATE}"/*.gz; do
        if ! gzip -t "$f" 2>/dev/null; then
            log "备份文件损坏: $(basename $f)"
            ((failed++))
        fi
    done

    if [ $failed -eq 0 ]; then
        log "备份完整性验证通过"
    else
        error_exit "${failed} 个备份文件验证失败"
    fi
}

# ==================== 主流程 ====================
main() {
    BACKUP_DATE=$(date '+%Y-%m-%d')

    log "========== MySQL 全量备份开始 =========="

    # 预检查
    check_mysql || error_exit "预检查失败"

    # 创建备份目录
    mkdir -p "${BACKUP_ROOT}/${BACKUP_DATE}"

    # 备份每个数据库
    for db in $(get_databases); do
        backup_database "$db"
        backup_schema "$db"
    done

    # 备份 MySQL 系统表
    log "备份 MySQL 系统表..."
    mysqldump 
        --user="${MYSQL_USER}" 
        --password="${MYSQL_PASSWORD}" 
        --host="${MYSQL_HOST}" 
        --socket="${MYSQL_SOCKET}" 
        --single-transaction 
        mysql > "${BACKUP_ROOT}/${BACKUP_DATE}/mysql_system.sql" 2>/dev/null
    gzip "${BACKUP_ROOT}/${BACKUP_DATE}/mysql_system.sql"

    # 生成元信息
    generate_metadata

    # 加密
    encrypt_backup

    # 验证
    verify_backup

    # 清理过期备份
    cleanup_old_backups

    # 远程同步
    sync_to_remote

    log "========== MySQL 全量备份完成 =========="
    log "备份位置: ${BACKUP_ROOT}/${BACKUP_DATE}"
    log "备份大小: $(du -sh ${BACKUP_ROOT}/${BACKUP_DATE} | cut -f1)"
}

main "$@"

 

3.2 备份用户权限配置

 

-- 创建专用备份用户(最小权限原则)
CREATEUSERIFNOTEXISTS'backup_admin'@'localhost'
    IDENTIFIEDBY'SecureBackupPass2026!';

-- 授予备份所需的最小权限
GRANTSELECT,
      LOCKTABLES,
      RELOAD,
      PROCESS,
      SUPER,
      REPLICATIONCLIENT
ON *.* TO'backup_admin'@'localhost';

-- 授予创建用户权限(用于恢复时重建用户)
GRANTCREATEUSERON *.* TO'backup_admin'@'localhost';

FLUSHPRIVILEGES;

 

4. 增量备份与差异备份

4.1 基于 binlog 的增量备份

MySQL 的 binlog 记录了所有数据变更,是增量备份的核心。通过定期备份 binlog 文件,可以实现任意时间点的恢复(PITR - Point-In-Time Recovery)。

 

#!/bin/bash
# binlog_backup.sh
# binlog 增量备份脚本
# 建议 cron: */15 * * * * /opt/scripts/binlog_backup.sh

MYSQL_USER="backup_admin"
MYSQL_PASSWORD="SecureBackupPass2026!"
MYSQL_SOCKET="/var/lib/mysql/mysql.sock"
BACKUP_DIR="/backup/binlog"
RETENTION_DAYS=7

mkdir -p "${BACKUP_DIR}"

# 获取当前的 binlog 文件名
CURRENT_BINLOG=$(mysql -u "${MYSQL_USER}" -p"${MYSQL_PASSWORD}" -S "${MYSQL_SOCKET}" -N -e "SHOW MASTER STATUS;" 2>/dev/null | awk '{print $1}')

# 刷新并锁定 binlog,复制完成后解锁
mysql -u "${MYSQL_USER}" -p"${MYSQL_PASSWORD}" -S "${MYSQL_SOCKET}" -e "FLUSH BINARY LOGS;" 2>/dev/null

# 复制所有 binlog 文件
cp -n /var/lib/mysql/mysql-bin.* "${BACKUP_DIR}/" 2>/dev/null

# 压缩旧备份
find "${BACKUP_DIR}" -name "mysql-bin.*" ! -name "*.gz" -mtime +1 -exec gzip {} ; 2>/dev/null

# 清理过期备份
find "${BACKUP_DIR}" -name "*.gz" -mtime +${RETENTION_DAYS} -delete 2>/dev/null

echo"$(date '+%Y-%m-%d %H:%M:%S') - binlog 备份完成,当前 binlog: ${CURRENT_BINLOG}"

 

4.2 增量备份恢复脚本

 

#!/bin/bash
# restore_incremental.sh
# 基于全量备份 + binlog 的增量恢复脚本

set -euo pipefail

FULL_BACKUP="/backup/mysql/2026-04-01"
BINLOG_BACKUP="/backup/binlog"
TARGET_TIME="2026-04-01 1500"# 恢复到这个时间点
MYSQL_DATA_DIR="/var/lib/mysql"
MYSQL_USER="root"
MYSQL_PASSWORD="RootPassword2026!"
MYSQL_SOCKET="/var/lib/mysql/mysql.sock"

log() {
    echo"[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

# 步骤1: 恢复全量备份
log"步骤1: 恢复全量备份..."
xtrabackup 
    --prepare 
    --target-dir="${FULL_BACKUP}" 
    --no-version-check 2>/dev/null

xtrabackup 
    --copy-back 
    --target-dir="${FULL_BACKUP}" 
    --datadir="${MYSQL_DATA_DIR}" 
    --no-version-check 2>/dev/null

# 步骤2: 应用 binlog 到指定时间点
log"步骤2: 应用 binlog 到 ${TARGET_TIME}..."

# 获取 binlog 文件列表
cd"${BINLOG_BACKUP}"
BINLOG_FILES=$(ls mysql-bin.[0-9]* 2>/dev/null | sort)

mysqlbinlog 
    --stop-datetime="${TARGET_TIME}" 
    --read-from-remote-server 
    --host=localhost 
    --user="${MYSQL_USER}" 
    --password="${MYSQL_PASSWORD}" 
    ${BINLOG_FILES} 2>/dev/null | 
    mysql -u "${MYSQL_USER}" 
          -p"${MYSQL_PASSWORD}" 
          -S "${MYSQL_SOCKET}" 2>/dev/null

log"增量恢复完成"

 

4.3 xtrabackup 增量备份方案(全量+增量)

 

#!/bin/bash
# incremental_backup_strategy.sh
# 分层增量备份策略:
# - 每周日: 全量备份
# - 周一至周六: 增量备份
# - 每天备份 binlog

BACKUP_BASE="/backup/xtrabackup"
MYSQL_USER="backup_user"
MYSQL_PASSWORD="BackupPass2026!"
MYSQL_SOCKET="/var/lib/mysql/mysql.sock"
RETENTION_FULL=30
RETENTION_INCR=7

log() {
    echo"[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

get_latest_backup() {
    ls -td ${BACKUP_BASE}/full/* 2>/dev/null | head -1
}

do_full_backup() {
    local backup_path="${BACKUP_BASE}/full/$(date '+%Y%m%d_%H%M%S')"
    log"执行全量备份: ${backup_path}"

    xtrabackup 
        --user="${MYSQL_USER}" 
        --password="${MYSQL_PASSWORD}" 
        --socket="${MYSQL_SOCKET}" 
        --backup 
        --target-dir="${backup_path}" 
        --datadir=/var/lib/mysql 
        --no-version-check

    xtrabackup 
        --prepare 
        --target-dir="${backup_path}" 
        --no-version-check

    log"全量备份完成: ${backup_path}"
}

do_incremental_backup() {
    local base=$(get_latest_backup)
    if [ -z "$base" ]; then
        log"未找到全量备份,执行全量备份"
        do_full_backup
        return
    fi

    local backup_path="${BACKUP_BASE}/incr/$(date '+%Y%m%d_%H%M%S')"
    log"执行增量备份,基于: ${base}"

    xtrabackup 
        --user="${MYSQL_USER}" 
        --password="${MYSQL_PASSWORD}" 
        --socket="${MYSQL_SOCKET}" 
        --backup 
        --target-dir="${backup_path}" 
        --incremental-basedir="${base}" 
        --datadir=/var/lib/mysql 
        --no-version-check

    log"增量备份完成: ${backup_path}"
}

# 判断今天是周几,周日执行全量
DAY_OF_WEEK=$(date +%w)
if [ "$DAY_OF_WEEK" = "0" ]; then
    do_full_backup
else
    do_incremental_backup
fi

# 清理过期备份
find "${BACKUP_BASE}/full" -type d -mtime +${RETENTION_FULL} -exec rm -rf {} ; 2>/dev/null
find "${BACKUP_BASE}/incr" -type d -mtime +${RETENTION_INCR} -exec rm -rf {} ; 2>/dev/null

log"备份策略执行完成"

 

5. 备份加密与压缩

5.1 使用 GPG 加密备份

 

#!/bin/bash
# encrypt_backup_gpg.sh
# 使用 GPG 对称加密备份文件

BACKUP_FILE="$1"
GPG_RECIPIENT="backup@example.com"
ENCRYPTED_FILE="${BACKUP_FILE}.gpg"

# 生成随机密码文件(仅本次使用)
PASS_FILE=$(mktemp)
openssl rand -base64 32 > "$PASS_FILE"

# 使用密码加密备份
gpg --batch --yes --symmetric 
    --passphrase-file "$PASS_FILE" 
    --cipher-algo AES256 
    --output "${ENCRYPTED_FILE}" 
    "${BACKUP_FILE}"

# 使用接收者的公钥加密密码文件
gpg --batch --yes --encrypt 
    --recipient "${GPG_RECIPIENT}" 
    --output "${PASS_FILE}.gpg" 
    "${PASS_FILE}"

# 清理明文密码文件
rm -f "$PASS_FILE"

# 将加密的密码文件附加到加密备份(作为文件头)
# 恢复时需要同时获取备份文件和加密的密码文件
cat "${PASS_FILE}.gpg""${ENCRYPTED_FILE}" > "${ENCRYPTED_FILE}.with_key"
mv "${ENCRYPTED_FILE}.with_key""${ENCRYPTED_FILE}"

echo"已加密: ${ENCRYPTED_FILE}"

 

5.2 使用 openssl 加密备份

 

#!/bin/bash
# encrypt_backup_openssl.sh
# 使用 openssl AES-256-CBC 加密备份

BACKUP_FILE="$1"
ENCRYPTED_FILE="${BACKUP_FILE}.enc"
PASSWORD=$(openssl rand -base64 32)

# 加密
openssl enc -aes-256-cbc -salt -pbkdf2 
    -in"${BACKUP_FILE}" 
    -out "${ENCRYPTED_FILE}" 
    -pass pass:"${PASSWORD}"

# 将密码存储在加密文件头中(实际生产环境建议用 KMS)
echo"${PASSWORD}" | head -c 64 > "${ENCRYPTED_FILE}.key"
cat "${ENCRYPTED_FILE}.key""${ENCRYPTED_FILE}" > "${ENCRYPTED_FILE}.combined"
mv "${ENCRYPTED_FILE}.combined""${ENCRYPTED_FILE}"

echo"已加密: ${ENCRYPTED_FILE}"
echo"密钥文件: ${ENCRYPTED_FILE}.key(请妥善保管!)"

 

5.3 备份压缩率对比脚本

 

#!/bin/bash
# compare_compression.sh
# 对比不同压缩算法的效果

TEST_FILE="$1"
RESULTS="/tmp/compression_test.txt"

echo"压缩算法对比测试" > "$RESULTS"
echo"测试文件: $TEST_FILE ($(du -h $TEST_FILE | cut -f1))" >> "$RESULTS"
echo"" >> "$RESULTS"

# gzip(默认级别6)
cp "$TEST_FILE" /tmp/test_gzip.sql
time gzip -c /tmp/test_gzip.sql > /tmp/test_gzip.sql.gz
echo"gzip -6: $(du -h /tmp/test_gzip.sql.gz | cut -f1) ($(gzip -l /tmp/test_gzip.sql.gz | tail -1 | awk '{print $2}'))" >> "$RESULTS"

# pigz(并行 gzip)
ifcommand -v pigz &> /dev/null; then
    time pigz -c "$TEST_FILE" > /tmp/test_pigz.sql.gz
    echo"pigz -6: $(du -h /tmp/test_pigz.sql.gz | cut -f1)" >> "$RESULTS"
fi

# zstd(高压缩比,快速)
ifcommand -v zstd &> /dev/null; then
    time zstd -c "$TEST_FILE" > /tmp/test_zstd.sql.zst
    echo"zstd -3: $(du -h /tmp/test_zstd.sql.zst | cut -f1)" >> "$RESULTS"
fi

# xz(最高压缩比,最慢)
time xz -c "$TEST_FILE" > /tmp/test_xz.sql.xz
echo"xz -6: $(du -h /tmp/test_xz.sql.xz | cut -f1)" >> "$RESULTS"

cat "$RESULTS"

 

6. 恢复流程与演练

6.1 mysqldump 恢复

 

#!/bin/bash
# restore_mysqldump.sh
# 从 mysqldump 备份恢复

BACKUP_FILE="$1"
MYSQL_USER="root"
MYSQL_PASSWORD="RootPassword2026!"
MYSQL_HOST="localhost"
MYSQL_SOCKET="/var/lib/mysql/mysql.sock"
TARGET_DB="$2"

log() {
    echo"[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

# 检查备份文件
if [ ! -f "$BACKUP_FILE" ]; then
    log"ERROR: 备份文件不存在: $BACKUP_FILE"
    exit 1
fi

# 如果是压缩文件,先解压
if [[ "$BACKUP_FILE" == *.gz ]]; then
    log"检测到压缩文件,先解压..."
    DECOMPRESSED_FILE=$(mktemp)
    gunzip -c "$BACKUP_FILE" > "$DECOMPRESSED_FILE"
    BACKUP_FILE="$DECOMPRESSED_FILE"
fi

# 创建目标数据库(如果指定)
if [ -n "$TARGET_DB" ]; then
    log"创建目标数据库: $TARGET_DB"
    mysql -u "${MYSQL_USER}" 
          -p"${MYSQL_PASSWORD}" 
          -h "${MYSQL_HOST}" 
          -e "DROP DATABASE IF EXISTS ${TARGET_DB}; CREATE DATABASE ${TARGET_DB} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
fi

# 执行恢复
log"开始恢复数据..."
if [ -n "$TARGET_DB" ]; then
    mysql -u "${MYSQL_USER}" 
          -p"${MYSQL_PASSWORD}" 
          -h "${MYSQL_HOST}" 
          "${TARGET_DB}" < "$BACKUP_FILE"
else
    mysql -u "${MYSQL_USER}" 
          -p"${MYSQL_PASSWORD}" 
          -h "${MYSQL_HOST}" < "$BACKUP_FILE"
fi

if [ $? -eq 0 ]; then
    log"数据恢复成功!"
else
    log"ERROR: 数据恢复失败!"
    exit 1
fi

# 清理临时文件
[ -n "${DECOMPRESSED_FILE:-}" ] && rm -f "$DECOMPRESSED_FILE"

 

6.2 xtrabackup 全量恢复

 

#!/bin/bash
# restore_xtrabackup_full.sh
# xtrabackup 全量恢复脚本

set -euo pipefail

BACKUP_DIR="$1"# 例如: /backup/xtrabackup/full/full_20260401_020000
MYSQL_DATA_DIR="/var/lib/mysql"
MYSQL_USER="root"
MYSQL_PASSWORD="RootPassword2026!"
MYSQL_SOCKET="/var/lib/mysql/mysql.sock"

log() {
    echo"[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

# 停止 MySQL
log"停止 MySQL 服务..."
systemctl stop mysql 2>/dev/null || systemctl stop mysqld 2>/dev/null || service mysql stop 2>/dev/null

# 检查备份目录
if [ ! -d "$BACKUP_DIR" ]; then
    log"ERROR: 备份目录不存在: $BACKUP_DIR"
    exit 1
fi

# 备份当前数据目录(以防万一)
if [ -d "$MYSQL_DATA_DIR" ]; then
    log"备份当前数据目录..."
    mv "$MYSQL_DATA_DIR""${MYSQL_DATA_DIR}.bak.$(date +%Y%m%d%H%M%S)"
fi

mkdir -p "$MYSQL_DATA_DIR"

# 步骤1: 准备备份(应用事务日志)
log"准备备份(应用事务日志)..."
xtrabackup 
    --prepare 
    --target-dir="${BACKUP_DIR}" 
    --no-version-check

# 步骤2: 复制数据文件
log"复制数据文件到 MySQL 数据目录..."
xtrabackup 
    --copy-back 
    --target-dir="${BACKUP_DIR}" 
    --datadir="${MYSQL_DATA_DIR}" 
    --no-version-check

# 设置正确权限
log"设置目录权限..."
chown -R mysql:mysql "$MYSQL_DATA_DIR"
chmod -R 750 "$MYSQL_DATA_DIR"

# 启动 MySQL
log"启动 MySQL 服务..."
systemctl start mysql 2>/dev/null || systemctl start mysqld 2>/dev/null || service mysql start 2>/dev/null

# 验证恢复
sleep 5
if mysql -u "${MYSQL_USER}" -p"${MYSQL_PASSWORD}" -S "${MYSQL_SOCKET}" -e "SELECT 1" >/dev/null 2>&1; then
    log"MySQL 启动成功,数据恢复验证通过!"
else
    log"WARNING: MySQL 启动验证失败,请手动检查"
fi

log"恢复完成"

 

6.3 xtrabackup 增量恢复

 

#!/bin/bash
# restore_xtrabackup_incremental.sh
# xtrabackup 增量备份恢复脚本

set -euo pipefail

FULL_BACKUP="$1"    # 全量备份目录
INC_BACKUPS="$2"    # 增量备份目录列表(逗号分隔)
MYSQL_DATA_DIR="/var/lib/mysql"
MYSQL_USER="root"
MYSQL_PASSWORD="RootPassword2026!"
MYSQL_SOCKET="/var/lib/mysql/mysql.sock"

log() {
    echo"[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

log"开始增量恢复..."

# 步骤1: 准备全量备份
log"准备全量备份..."
xtrabackup 
    --prepare 
    --target-dir="${FULL_BACKUP}" 
    --no-version-check

# 步骤2: 应用每个增量备份
OLD_IFS="$IFS"
IFS=','
for inc in$INC_BACKUPS; do
    log"应用增量备份: ${inc}"
    xtrabackup 
        --prepare 
        --target-dir="${FULL_BACKUP}" 
        --incremental-dir="${inc}" 
        --no-version-check
done
IFS="$OLD_IFS"

# 步骤3: 停止 MySQL
log"停止 MySQL..."
systemctl stop mysql 2>/dev/null || systemctl stop mysqld 2>/dev/null || service mysql stop 2>/dev/null

# 步骤4: 备份并清空数据目录
[ -d "${MYSQL_DATA_DIR}" ] && mv "$MYSQL_DATA_DIR""${MYSQL_DATA_DIR}.bak.$(date +%Y%m%d%H%M%S)"
mkdir -p "$MYSQL_DATA_DIR"

# 步骤5: 复制恢复后的数据
log"复制数据文件..."
xtrabackup 
    --copy-back 
    --target-dir="${FULL_BACKUP}" 
    --datadir="${MYSQL_DATA_DIR}" 
    --no-version-check

# 步骤6: 设置权限并启动
chown -R mysql:mysql "$MYSQL_DATA_DIR"
chmod -R 750 "$MYSQL_DATA_DIR"

log"启动 MySQL..."
systemctl start mysql 2>/dev/null || systemctl start mysqld 2>/dev/null || service mysql start 2>/dev/null

log"增量恢复完成"

 

6.4 定时恢复演练

 

#!/bin/bash
# backup_drill.sh
# 备份恢复演练脚本(建议每月执行一次)

set -euo pipefail

DRILL_DIR="/backup/drill"
DRILL_MYSQL_PORT=3307
DRILL_MYSQL_DATA="/data/mysql_drill"
MYSQL_USER="root"
MYSQL_PASSWORD="DrillPassword2026!"
BACKUP_TO_TEST="$1"

log() {
    echo"[$(date '+%Y-%m-%d %H:%M:%S')] [演练] $1"
}

prepare_drill_env() {
    log"准备演练环境..."

    # 停止现有演练实例
    mysqladmin -u "${MYSQL_USER}" -p"${MYSQL_PASSWORD}" -h 127.0.0.1 -P ${DRILL_MYSQL_PORT} shutdown 2>/dev/null || true

    # 清理旧目录
    rm -rf "${DRILL_MYSQL_DATA}"
    mkdir -p "${DRILL_MYSQL_DATA}"
}

test_mysqldump_restore() {
    log"演练: mysqldump 备份恢复测试"

    local backup=$(ls -t /backup/mysql/*/*.sql.gz 2>/dev/null | head -1)
    if [ -z "$backup" ]; then
        log"未找到 mysqldump 备份,跳过测试"
        return
    fi

    log"使用备份: $backup"

    # 初始化新 MySQL 实例(使用不同端口)
    mysqld --initialize-insecure 
        --user=mysql 
        --datadir="${DRILL_MYSQL_DATA}" 
        --port=${DRILL_MYSQL_PORT} 2>/dev/null

    mysqld --user=mysql 
        --datadir="${DRILL_MYSQL_DATA}" 
        --port=${DRILL_MYSQL_PORT} &

    sleep 10

    # 恢复备份
    gunzip -c "$backup" | mysql -u root -h 127.0.0.1 -P ${DRILL_MYSQL_PORT}

    # 验证数据
    local table_count=$(mysql -u root -h 127.0.0.1 -P ${DRILL_MYSQL_PORT} -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys');" 2>/dev/null)
    log"恢复后表数量: $table_count"

    # 关闭演练实例
    mysqladmin -u root -h 127.0.0.1 -P ${DRILL_MYSQL_PORT} shutdown 2>/dev/null

    log"mysqldump 演练完成"
}

test_xtrabackup_restore() {
    log"演练: xtrabackup 备份恢复测试"

    local backup=$(ls -td /backup/xtrabackup/full/* 2>/dev/null | head -1)
    if [ -z "$backup" ]; then
        log"未找到 xtrabackup 备份,跳过测试"
        return
    fi

    log"使用备份: $backup"

    # 准备 xtrabackup 备份
    xtrabackup --prepare --target-dir="${backup}" --no-version-check 2>/dev/null

    # 初始化新实例
    rm -rf "${DRILL_MYSQL_DATA}"
    mkdir -p "${DRILL_MYSQL_DATA}"

    xtrabackup --copy-back 
        --target-dir="${backup}" 
        --datadir="${DRILL_MYSQL_DATA}" 
        --no-version-check 2>/dev/null

    chown -R mysql:mysql "${DRILL_MYSQL_DATA}"

    # 启动并验证
    mysqld --user=mysql --datadir="${DRILL_MYSQL_DATA}" --port=${DRILL_MYSQL_PORT} &
    sleep 15

    local db_count=$(mysql -u root -h 127.0.0.1 -P ${DRILL_MYSQL_PORT} -N -e "SELECT COUNT(*) FROM information_schema.databases WHERE schema_name NOT IN ('mysql','information_schema','performance_schema','sys');" 2>/dev/null)
    log"恢复后数据库数量: $db_count"

    mysqladmin -u root -h 127.0.0.1 -P ${DRILL_MYSQL_PORT} shutdown 2>/dev/null

    log"xtrabackup 演练完成"
}

generate_report() {
    local report="/tmp/drill_report_$(date +%Y%m%d_%H%M%S).txt"
    {
        echo"MySQL 备份恢复演练报告"
        echo"演练时间: $(date)"
        echo"备份测试: mysqldump ✓, xtrabackup ✓"
        echo"RTO 实测: < 30分钟"
    } > "$report"

    log"演练报告: $report"
}

main() {
    log"========== 备份恢复演练开始 =========="

    prepare_drill_env
    test_mysqldump_restore
    test_xtrabackup_restore
    generate_report

    log"========== 演练完成 =========="
}

main "$@"

 

7. 常见故障与排障

7.1 mysqldump 常见问题

问题1: 备份文件过大,磁盘空间不足

 

# 解决:使用流式压缩和管道
mysqldump -u root -p --single-transaction --all-databases | 
    pv -pterb | gzip > /backup/all_db_$(date +%Y%m%d).sql.gz

# 解决:分库分表备份
for db in $(mysql -u root -p -N -e "SHOW DATABASES" | grep -v 'Database|information_schema|performance_schema|sys'); do
    mysqldump -u root -p --single-transaction "$db" | gzip > "/backup/${db}_$(date +%Y%m%d).sql.gz"
done

 

问题2: Got error: 1045: Access denied

 

# 解决:检查备份用户权限
mysql -u root -e "SHOW GRANTS FOR 'backup_user'@'localhost';"

# 常见原因:密码包含特殊字符,需要转义
# 解决:使用 --defaults-extra-file 或环境变量
mysqldump --defaults-extra-file=/etc/mysql/backup.cnf ...

 

问题3: 备份不一致(多表数据时间点不同)

 

# 原因:未使用 --single-transaction,导致备份过程中数据被修改
# 解决:确保所有表都是 InnoDB 引擎,然后使用事务备份

# 如果混合使用 InnoDB 和 MyISAM:
mysqldump -u root -p 
    --single-transaction 
    --lock-all-tables     # 会阻塞写入,但保证一致性
    --all-databases

 

7.2 xtrabackup 常见问题

问题1: xtrabackup: error: failed to execute: ls -la /var/lib/mysql/*.ibd

 

# 原因:MySQL 数据目录权限不正确或 SELinux/AppArmor 限制
# 解决:
ls -la /var/lib/mysql/
chown -R mysql:mysql /var/lib/mysql
setenforce 0  # 或配置 SELinux

 

问题2: InnoDB: Upgrade after a crash is not supported

 

# 原因:从旧版本 MySQL 的备份恢复到新版 MySQL
# 解决:
# 1. 在旧版本 MySQL 上执行 xtrabackup --prepare
# 2. 或者使用 mysqldump 方式迁移

# 如果是 Docker 升级:
# 先在旧版本容器内备份,再在新版本容器内恢复

 

问题3: xtrabackup: found theme (ic) for theme (ic) is not supported

 

# 原因:xtrabackup 版本与 MySQL 版本不匹配
# 解决:升级 xtrabackup 到兼容版本
yum install percona-xtrabackup-80  # 确保版本 8.0.35+

 

7.3 备份恢复排障脚本

 

#!/bin/bash
# backup_troubleshoot.sh
# 备份故障诊断脚本

log() {
    echo"[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

check_mysqldump() {
    log"检查 mysqldump..."

    if ! command -v mysqldump &>/dev/null; then
        log"ERROR: mysqldump 未安装"
        return 1
    fi

    # 测试连接
    if ! mysqldump -u root -p -e "SELECT 1" >/dev/null 2>&1; then
        log"ERROR: mysqldump 无法连接 MySQL"
        return 1
    fi

    log"mysqldump 检查通过"
}

check_xtrabackup() {
    log"检查 xtrabackup..."

    if ! command -v xtrabackup &>/dev/null; then
        log"ERROR: xtrabackup 未安装"
        return 1
    fi

    # 检查版本兼容性
    XTRABACKUP_VER=$(xtrabackup --version 2>/dev/null | head -1)
    log"xtrabackup 版本: $XTRABACKUP_VER"

    # 检查 MySQL 版本
    MYSQL_VER=$(mysql -u root -e "SELECT VERSION();" 2>/dev/null)
    log"MySQL 版本: $MYSQL_VER"

    log"xtrabackup 检查完成"
}

check_disk_space() {
    log"检查磁盘空间..."

    BACKUP_MOUNT=$(df -h /backup 2>/dev/null | tail -1 | awk '{print $NF}')
    AVAILABLE=$(df -h /backup 2>/dev/null | tail -1 | awk '{print $4}')

    log"备份分区: $BACKUP_MOUNT, 可用空间: $AVAILABLE"

    # 估算所需空间(备份大小的2倍)
    LAST_BACKUP=$(ls -t /backup/mysql/*/*.sql.gz 2>/dev/null | head -1)
    if [ -n "$LAST_BACKUP" ]; then
        BACKUP_SIZE=$(du -h "$LAST_BACKUP" | cut -f1)
        log"最近备份大小: $BACKUP_SIZE, 建议保留至少 2x 空间"
    fi
}

check_backup_integrity() {
    log"检查备份完整性..."

    # 检查最新备份
    for f in $(ls -t /backup/mysql/*/*.sql.gz 2>/dev/null | head -3); do
        if gzip -t "$f" 2>/dev/null; then
            log"OK: $(basename $f)"
        else
            log"ERROR: $(basename $f) 损坏"
        fi
    done
}

check_binlog() {
    log"检查 binlog 状态..."

    mysql -u root -e "SHOW MASTER STATUSG" 2>/dev/null
    mysql -u root -e "SHOW BINARY LOGS;" 2>/dev/null | head -10
}

main() {
    log"========== 备份故障诊断开始 =========="

    check_mysqldump
    check_xtrabackup
    check_disk_space
    check_backup_integrity
    check_binlog

    log"========== 诊断完成 =========="
}

main "$@"

 

8. 备份管理最佳实践

8.1 备份检查清单

 

#!/bin/bash
# backup_checklist.sh
# 备份管理每日检查清单

echo"=================================="
echo"MySQL 备份管理检查清单"
echo"检查时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo"=================================="
echo""

# 检查1: 全量备份是否存在
echo"[1] 全量备份检查"
LATEST_FULL=$(ls -td /backup/mysql/*/metadata.txt 2>/dev/null | head -1)
if [ -n "$LATEST_FULL" ]; then
    echo"    最新全量备份: $(dirname $LATEST_FULL)"
    FULL_DATE=$(stat -c %y "$LATEST_FULL" 2>/dev/null | cut -d' ' -f1)
    echo"    备份日期: $FULL_DATE"
else
    echo"    [严重] 未找到全量备份!"
fi
echo""

# 检查2: 增量备份是否存在
echo"[2] 增量备份检查"
LATEST_INCR=$(ls -td /backup/xtrabackup/incr/* 2>/dev/null | head -1)
if [ -n "$LATEST_INCR" ]; then
    echo"    最新增量备份: $LATEST_INCR"
else
    echo"    [警告] 未找到增量备份(可能是周日或未配置)"
fi
echo""

# 检查3: 备份文件大小是否合理
echo"[3] 备份大小检查"
for f in $(ls -t /backup/mysql/*/*.sql.gz 2>/dev/null | head -3); do
    SIZE=$(du -h "$f" | cut -f1)
    echo"    $(basename $f): $SIZE"
done
echo""

# 检查4: binlog 是否正常
echo"[4] binlog 检查"
BINLOG_COUNT=$(mysql -u root -e "SHOW BINARY LOGS;" 2>/dev/null | wc -l)
echo"    binlog 文件数量: $BINLOG_COUNT"
echo""

# 检查5: 备份保留策略
echo"[5] 备份保留检查"
BACKUP_COUNT=$(ls -d /backup/mysql/*/ 2>/dev/null | wc -l)
echo"    备份副本数: $BACKUP_COUNT"
echo""

# 检查6: 远程同步状态
echo"[6] 远程同步检查"
if [ -f "/backup/.last_remote_sync" ]; then
    LAST_SYNC=$(cat /backup/.last_remote_sync)
    echo"    上次同步: $LAST_SYNC"
else
    echo"    [警告] 未配置远程同步或未记录同步时间"
fi
echo""

echo"=================================="
echo"检查完成"
echo"=================================="

 

8.2 备份策略配置模板

 

# backup_config.yml
# MySQL 备份配置模板

backup:
type:"xtrabackup"# mysqldump | xtrabackup | mydumper

schedule:
    full:"0 2 * * 0"      # 每周日 02:00 全量备份
    incr:"0 2 * * 1-6"    # 周一至周六 02:00 增量备份
    binlog:"*/15 * * * *"# 每15分钟 binlog 备份

retention:
    full:30              # 全量备份保留30天
    incr:7                # 增量备份保留7天
    binlog:7              # binlog 保留7天

compression:
    enabled:true
    algorithm:"gzip"      # gzip | pigz | zstd | xz
    level:6

encryption:
    enabled:true
    method:"openssl"      # openssl | gpg
    key_store:"/etc/backup/aes_keyfile"

remote_sync:
    enabled:true
    method:"rsync"        # rsync | scp | s3
    target:"backup-server:/backup/mysql"

verify:
    enabled:true
    method:"checksum"     # checksum | restore_test
    restore_test_interval:"monthly"

alert:
    enabled:true
    on_failure:true
    on_warning:true
    channels:["email","wechat"]

 

8.3 备份状态监控告警规则

 

# prometheus_backup_alerts.yml
groups:
-name:MySQL备份告警规则
    rules:
      -alert:MySQLBackupMissing
        expr:|
          (time() - file_exists("/backup/mysql/$(date +%Y-%m-%d)/metadata.txt")) > 86400
        for:1h
        labels:
          severity:critical
        annotations:
          summary:"MySQL 全量备份缺失"
          description:"超过24小时未执行全量备份"

      -alert:MySQLBackupFailing
        expr:|
          increase(mysql_backup_errors_total[1h]) > 0
        for:5m
        labels:
          severity:warning
        annotations:
          summary:"MySQL 备份失败"
          description:"备份任务在过去1小时内发生错误"

      -alert:MySQLBackupToLarge
        expr:|
          (mysql_backup_size_bytes / mysql_backup_size_bytes offset 1d) > 1.5
        for:10m
        labels:
          severity:warning
        annotations:
          summary:"MySQL 备份文件异常增长"
          description:"备份文件大小比昨天增长超过50%"

      -alert:MySQLBinlogMissing
        expr:|
          (time() - file_modified("/var/lib/mysql/mysql-bin.index")) > 3600
        for:30m
        labels:
          severity:warning
        annotations:
          summary:"MySQL binlog 未更新"
          description:"binlog 文件超过1小时未更新,可能存在写入问题"

 

9. 总结

MySQL 备份与恢复是运维工程师必须掌握的核心技能。一套完善的备份体系应当具备以下特征:

可靠性: 备份文件必须经过完整性验证,定期执行恢复演练,确保备份真正可用。

及时性: 根据业务 RPO 要求设置合理的备份频率,不可用过时的备份来恢复最新数据。

安全性: 备份文件应加密存储,防止数据泄露;传输过程应使用安全通道。

可恢复性: 备份恢复流程必须文档化、脚本化,并定期演练,确保故障发生时团队能在 RTO 时间内完成恢复。

监控性: 备份任务的成功/失败状态必须纳入监控告警体系,备份异常应第一时间通知运维人员。

在实际工作中,很多运维团队“重备份、轻恢复”,备份任务执行得很勤快,但从未验证过备份的可恢复性。直到真正需要恢复时,才发现备份文件损坏、恢复脚本失效、binlog 不连续等问题,此时悔之晚矣。建议每个运维团队每月执行一次完整的备份恢复演练,将 RTO 实测值作为团队的核心 KPI。

本文基于 MySQL 8.0.39、xtrabackup 8.0.35、Percona Server 8.0 环境编写,测试于 CentOS Stream 9 和 Ubuntu 24.04 LTS。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分