Skip to content

MySQL 增量恢复

增量恢复是在全量恢复的基础上,恢复自全量备份以来的增量数据,用于将数据库恢复到更接近故障发生时的状态。本文将详细介绍MySQL增量恢复的概念、恢复方法、步骤和生产环境最佳实践。

增量恢复概述

什么是增量恢复

增量恢复是指在全量恢复的基础上,应用增量备份或二进制日志,将数据库恢复到特定时间点或特定事务的状态。它是全量恢复的补充,可以进一步减少数据丢失,降低RPO(恢复点目标)。

增量恢复的类型

根据使用的备份文件类型,增量恢复可以分为:

  • 基于增量备份的恢复:使用xtrabackup等工具生成的增量备份文件进行恢复
  • 基于二进制日志的恢复:使用MySQL的二进制日志进行恢复,也称为时间点恢复(PITR)

增量恢复的应用场景

  • 减少数据丢失:在全量恢复的基础上,应用增量数据,将数据丢失减少到最小
  • 恢复到特定时间点:将数据库恢复到特定的时间点,如数据误删除前的时间点
  • 恢复到特定事务:将数据库恢复到特定事务执行前的状态
  • 应对频繁数据变化:对于数据变化频繁的数据库,使用增量恢复可以提高恢复效率

版本差异考虑

不同MySQL版本的增量恢复特性存在差异:

特性MySQL 5.6MySQL 5.7MySQL 8.0
增量备份支持支持支持支持
二进制日志格式STATEMENT, ROW, MIXEDSTATEMENT, ROW, MIXEDROW(默认)
日志恢复速度较慢较快
并行应用日志不支持支持支持
克隆插件支持不支持不支持支持

基于增量备份的恢复

恢复前准备

环境检查

bash
# 1. 检查MySQL服务状态
mysqladmin -u root -p ping

# 2. 检查全量备份和增量备份文件
ls -la /backup/mysql/full/
ls -la /backup/mysql/incremental/

# 3. 检查磁盘空间
df -h /var/lib/mysql

# 4. 检查备份文件完整性
xtrabackup --prepare --target-dir=/backup/mysql/full/20231201/
xtrabackup --prepare --target-dir=/backup/mysql/incremental/20231202/ --incremental-basedir=/backup/mysql/full/20231201/

恢复顺序

基于增量备份的恢复必须按照以下顺序进行:

  1. 恢复全量备份
  2. 按照时间顺序应用第一个增量备份
  3. 按照时间顺序应用第二个增量备份
  4. 以此类推,应用所有增量备份
  5. 最后一次准备,完成恢复

恢复步骤

使用xtrabackup增量备份的恢复

bash
# 1. 停止MySQL服务
systemctl stop mysqld

# 2. 备份当前数据目录(可选)
mv /var/lib/mysql /var/lib/mysql_bak
mkdir -p /var/lib/mysql

# 3. 准备全量备份
xtrabackup --prepare --apply-log-only --target-dir=/backup/mysql/full/20231201/

# 4. 应用第一个增量备份
xtrabackup --prepare --apply-log-only --target-dir=/backup/mysql/full/20231201/ --incremental-dir=/backup/mysql/incremental/20231202/

# 5. 应用第二个增量备份(如果有)
xtrabackup --prepare --apply-log-only --target-dir=/backup/mysql/full/20231201/ --incremental-dir=/backup/mysql/incremental/20231203/

# 6. 最后一次准备,不使用--apply-log-only
xtrabackup --prepare --target-dir=/backup/mysql/full/20231201/

# 7. 恢复合并后的备份
xtrabackup --copy-back --target-dir=/backup/mysql/full/20231201/ --datadir=/var/lib/mysql

# 8. 设置权限
chown -R mysql:mysql /var/lib/mysql

# 9. 启动MySQL服务
systemctl start mysqld

# 10. 验证恢复结果
mysql -u root -p -e "SHOW DATABASES;"

恢复后验证

bash
# 1. 验证数据库服务状态
systemctl status mysqld

# 2. 验证数据完整性
mysql -u root -p -e "USE dbname; SELECT COUNT(*) FROM users;"

# 3. 验证增量数据是否已恢复
# 假设我们知道增量备份中包含的特定数据
mysql -u root -p -e "USE dbname; SELECT * FROM users WHERE created_at >= '2023-12-02 00:00:00';"

# 4. 验证应用程序功能
# 使用应用程序测试连接到数据库

基于二进制日志的恢复

恢复前准备

环境检查

bash
# 1. 检查MySQL服务状态
mysqladmin -u root -p ping

# 2. 检查二进制日志文件
ls -la /var/lib/mysql/binlog.*

# 3. 检查全量备份文件
ls -la /backup/mysql/full/

# 4. 检查磁盘空间
df -h /var/lib/mysql

确定恢复时间点

在进行基于二进制日志的恢复前,需要确定恢复的目标时间点或事务:

  • 基于时间点的恢复:恢复到特定的时间点,如 "2023-12-02 14:30:00"
  • 基于事务的恢复:恢复到特定事务之前,使用事务ID(GTID)或二进制日志位置

恢复步骤

基于时间点的恢复

bash
# 1. 先进行全量恢复(略)

# 2. 查看二进制日志文件列表
mysql -u root -p -e "SHOW BINARY LOGS;"

# 3. 查看二进制日志内容,确定恢复的起始和结束位置
mysqlbinlog --start-datetime="2023-12-01 02:00:00" --stop-datetime="2023-12-02 14:30:00" /var/lib/mysql/binlog.000001 > /tmp/binlog.sql

# 4. 应用二进制日志
mysql -u root -p dbname < /tmp/binlog.sql

# 5. 验证恢复结果
mysql -u root -p -e "USE dbname; SELECT COUNT(*) FROM users;"

基于GTID的恢复

bash
# 1. 先进行全量恢复(略)

# 2. 启用GTID模式
mysql -u root -p -e "SET GLOBAL gtid_mode = ON; SET GLOBAL enforce_gtid_consistency = ON;"

# 3. 查看当前GTID执行情况
mysql -u root -p -e "SHOW MASTER STATUS;"
mysql -u root -p -e "SELECT @@GLOBAL.gtid_executed;"

# 4. 应用二进制日志到特定GTID
mysqlbinlog --include-gtids="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-100" /var/lib/mysql/binlog.000001 | mysql -u root -p

# 5. 验证恢复结果
mysql -u root -p -e "USE dbname; SELECT COUNT(*) FROM users;"
mysql -u root -p -e "SELECT @@GLOBAL.gtid_executed;"

恢复后验证

bash
# 1. 验证数据完整性
mysql -u root -p -e "USE dbname; SELECT COUNT(*) FROM users;"

# 2. 验证特定时间点的数据
# 假设我们恢复到2023-12-02 14:30:00
mysql -u root -p -e "USE dbname; SELECT * FROM users WHERE created_at <= '2023-12-02 14:30:00' ORDER BY created_at DESC LIMIT 10;"

# 3. 验证数据库服务状态
mysqladmin -u root -p status

# 4. 验证应用程序功能
# 使用应用程序测试连接到数据库

自动恢复脚本

基于增量备份的自动恢复脚本

bash
#!/bin/bash

# 配置信息
FULL_BACKUP_DIR="/backup/mysql/full/20231201"
INCREMENTAL_DIR="/backup/mysql/incremental"
DB_DATA_DIR="/var/lib/mysql"
MYSQL_SERVICE="mysqld"
LOG_FILE="/backup/mysql/incremental_recovery.log"

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

log "Starting incremental recovery using xtrabackup"
log "Full backup directory: $FULL_BACKUP_DIR"
log "Incremental backup directory: $INCREMENTAL_DIR"
log "Target data directory: $DB_DATA_DIR"

# 1. 检查全量备份目录存在性
if [ ! -d "$FULL_BACKUP_DIR" ]; then
  log "ERROR: Full backup directory not found: $FULL_BACKUP_DIR"
  exit 1
fi

# 2. 获取增量备份列表(按时间排序)
INCREMENTAL_BACKUPS=$(ls -td $INCREMENTAL_DIR/*/ 2>/dev/null | grep -v "\.$" | sort)

if [ -z "$INCREMENTAL_BACKUPS" ]; then
  log "ERROR: No incremental backup directories found in: $INCREMENTAL_DIR"
  exit 1
fi

# 3. 停止MySQL服务
log "Stopping MySQL service: $MYSQL_SERVICE"
systemctl stop $MYSQL_SERVICE

if [ $? -ne 0 ]; then
  log "ERROR: Failed to stop MySQL service"
  exit 1
fi

# 4. 备份当前数据目录(可选)
BAK_DIR="${DB_DATA_DIR}_$(date +'%Y%m%d%H%M%S')"
log "Backing up current data directory to: $BAK_DIR"
mv $DB_DATA_DIR $BAK_DIR
mkdir -p $DB_DATA_DIR

if [ $? -ne 0 ]; then
  log "ERROR: Failed to backup current data directory"
  systemctl start $MYSQL_SERVICE
  exit 1
fi

# 5. 准备全量备份
log "Preparing full backup"
xtrabackup --prepare --apply-log-only --target-dir=$FULL_BACKUP_DIR > /tmp/xtrabackup_prepare_full.log 2>&1

if [ $? -ne 0 ]; then
  log "ERROR: Failed to prepare full backup"
  cat /tmp/xtrabackup_prepare_full.log >> $LOG_FILE
  rm -f /tmp/xtrabackup_prepare_full.log
  # 恢复原数据目录
  rm -rf $DB_DATA_DIR
  mv $BAK_DIR $DB_DATA_DIR
  systemctl start $MYSQL_SERVICE
  exit 1
fi

# 6. 应用所有增量备份
for INC_BACKUP in $INCREMENTAL_BACKUPS; do
  log "Applying incremental backup: $INC_BACKUP"
  xtrabackup --prepare --apply-log-only --target-dir=$FULL_BACKUP_DIR --incremental-dir=$INC_BACKUP > /tmp/xtrabackup_prepare_inc.log 2>&1
  
  if [ $? -ne 0 ]; then
    log "ERROR: Failed to apply incremental backup: $INC_BACKUP"
    cat /tmp/xtrabackup_prepare_inc.log >> $LOG_FILE
    rm -f /tmp/xtrabackup_prepare_full.log /tmp/xtrabackup_prepare_inc.log
    # 恢复原数据目录
    rm -rf $DB_DATA_DIR
    mv $BAK_DIR $DB_DATA_DIR
    systemctl start $MYSQL_SERVICE
    exit 1
  fi
  
  rm -f /tmp/xtrabackup_prepare_inc.log
done

# 7. 最后一次准备,不使用--apply-log-only
log "Final preparation of backup"
xtrabackup --prepare --target-dir=$FULL_BACKUP_DIR > /tmp/xtrabackup_prepare_final.log 2>&1

if [ $? -ne 0 ]; then
  log "ERROR: Failed to finalize backup preparation"
  cat /tmp/xtrabackup_prepare_final.log >> $LOG_FILE
  rm -f /tmp/xtrabackup_prepare_full.log /tmp/xtrabackup_prepare_final.log
  # 恢复原数据目录
  rm -rf $DB_DATA_DIR
  mv $BAK_DIR $DB_DATA_DIR
  systemctl start $MYSQL_SERVICE
  exit 1
fi

# 8. 恢复备份
log "Copying backup to data directory"
xtrabackup --copy-back --target-dir=$FULL_BACKUP_DIR --datadir=$DB_DATA_DIR > /tmp/xtrabackup_copy.log 2>&1

if [ $? -ne 0 ]; then
  log "ERROR: Failed to copy backup to data directory"
  cat /tmp/xtrabackup_copy.log >> $LOG_FILE
  rm -f /tmp/xtrabackup_prepare_full.log /tmp/xtrabackup_prepare_final.log /tmp/xtrabackup_copy.log
  # 恢复原数据目录
  rm -rf $DB_DATA_DIR
  mv $BAK_DIR $DB_DATA_DIR
  systemctl start $MYSQL_SERVICE
  exit 1
fi

# 9. 设置权限
log "Setting permissions on data directory"
chown -R mysql:mysql $DB_DATA_DIR

if [ $? -ne 0 ]; then
  log "ERROR: Failed to set permissions"
  # 恢复原数据目录
  rm -rf $DB_DATA_DIR
  mv $BAK_DIR $DB_DATA_DIR
  systemctl start $MYSQL_SERVICE
  exit 1
fi

# 10. 启动MySQL服务
log "Starting MySQL service: $MYSQL_SERVICE"
systemctl start $MYSQL_SERVICE

if [ $? -ne 0 ]; then
  log "ERROR: Failed to start MySQL service"
  # 恢复原数据目录
  rm -rf $DB_DATA_DIR
  mv $BAK_DIR $DB_DATA_DIR
  systemctl start $MYSQL_SERVICE
  exit 1
fi

# 11. 验证MySQL服务状态
log "Verifying MySQL service status"
sleep 10

if systemctl is-active $MYSQL_SERVICE > /dev/null 2>&1; then
  log "PASS: MySQL service is running"
else
  log "ERROR: MySQL service failed to start"
  # 恢复原数据目录
  systemctl stop $MYSQL_SERVICE 2>/dev/null
  rm -rf $DB_DATA_DIR
  mv $BAK_DIR $DB_DATA_DIR
  systemctl start $MYSQL_SERVICE
  exit 1
fi

# 12. 验证数据库存在性
DB_COUNT=$(mysql -u root -S /var/lib/mysql/mysql.sock -e "SHOW DATABASES;" | grep -v "Database" | wc -l)
if [ $DB_COUNT -gt 0 ]; then
  log "PASS: Found $DB_COUNT databases"
else
  log "WARN: No databases found"
fi

# 13. 清理临时文件
rm -f /tmp/xtrabackup_prepare_full.log /tmp/xtrabackup_prepare_final.log /tmp/xtrabackup_copy.log

log "Incremental recovery completed successfully"
exit 0

基于二进制日志的自动恢复脚本

bash
#!/bin/bash

# 配置信息
FULL_BACKUP_FILE="/backup/mysql/full/mysql_full_20231201.sql.gz"
DB_USER="root"
DB_PASS="your_password"
DB_NAME="dbname"
START_DATETIME="2023-12-01 02:00:00"
STOP_DATETIME="2023-12-02 14:30:00"
LOG_FILE="/backup/mysql/binlog_recovery.log"

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

log "Starting binary log recovery for database: $DB_NAME"
log "Using full backup file: $FULL_BACKUP_FILE"
log "Recovering from $START_DATETIME to $STOP_DATETIME"

# 1. 检查全量备份文件存在性
if [ ! -f "$FULL_BACKUP_FILE" ]; then
  log "ERROR: Full backup file not found: $FULL_BACKUP_FILE"
  exit 1
fi

# 2. 检查MySQL服务状态
if mysqladmin -u $DB_USER -p$DB_PASS ping > /dev/null 2>&1; then
  log "PASS: MySQL service is running"
else
  log "ERROR: MySQL service is not running"
  exit 1
fi

# 3. 先进行全量恢复
log "Starting full recovery"

# 3.1 创建数据库(如果不存在)
if ! mysql -u $DB_USER -p$DB_PASS -e "SHOW DATABASES LIKE '$DB_NAME';" | grep -q $DB_NAME; then
  log "Creating database: $DB_NAME"
  mysql -u $DB_USER -p$DB_PASS -e "CREATE DATABASE $DB_NAME;"
  
  if [ $? -ne 0 ]; then
    log "ERROR: Failed to create database: $DB_NAME"
    exit 1
  fi
fi

# 3.2 执行全量恢复
gunzip -c $FULL_BACKUP_FILE | mysql -u $DB_USER -p$DB_PASS $DB_NAME

if [ $? -ne 0 ]; then
  log "ERROR: Full recovery failed"
  exit 1
fi

log "PASS: Full recovery completed successfully"

# 4. 获取二进制日志文件列表
BINLOGS=$(mysql -u $DB_USER -p$DB_PASS -e "SHOW BINARY LOGS;" | grep -v "Log_name" | awk '{print $1}')

if [ -z "$BINLOGS" ]; then
  log "ERROR: No binary log files found"
  exit 1
fi

# 5. 应用二进制日志
log "Starting binary log recovery from $START_DATETIME to $STOP_DATETIME"

# 创建临时目录存储二进制日志内容
TMP_DIR="/tmp/binlog_recovery_$(date +'%Y%m%d%H%M%S')"
mkdir -p $TMP_DIR

# 提取并合并所有二进制日志
for BINLOG in $BINLOGS; do
  log "Processing binary log: $BINLOG"
  mysqlbinlog --start-datetime="$START_DATETIME" --stop-datetime="$STOP_DATETIME" /var/lib/mysql/$BINLOG >> $TMP_DIR/merged_binlog.sql
  
  if [ $? -ne 0 ]; then
    log "ERROR: Failed to process binary log: $BINLOG"
    rm -rf $TMP_DIR
    exit 1
  fi
done

# 6. 应用合并后的二进制日志
if [ -s "$TMP_DIR/merged_binlog.sql" ]; then
  log "Applying merged binary log file"
  mysql -u $DB_USER -p$DB_PASS $DB_NAME < $TMP_DIR/merged_binlog.sql
  
  if [ $? -ne 0 ]; then
    log "ERROR: Failed to apply binary log"
    rm -rf $TMP_DIR
    exit 1
  fi
  
  log "PASS: Binary log recovery completed successfully"
else
  log "WARN: Merged binary log file is empty, no data to recover"
fi

# 7. 恢复后验证
log "Verifying recovery results"

# 7.1 验证表存在性
TABLE_COUNT=$(mysql -u $DB_USER -p$DB_PASS -e "USE $DB_NAME; SHOW TABLES;" | grep -v "Tables_in_" | wc -l)
if [ $TABLE_COUNT -gt 0 ]; then
  log "PASS: Found $TABLE_COUNT tables in database $DB_NAME"
else
  log "WARN: No tables found in database $DB_NAME"
fi

# 7.2 验证数据完整性
# 假设我们知道users表应该有数据
if mysql -u $DB_USER -p$DB_PASS -e "USE $DB_NAME; SHOW TABLES LIKE 'users';" | grep -q users; then
  USER_COUNT=$(mysql -u $DB_USER -p$DB_PASS -e "USE $DB_NAME; SELECT COUNT(*) FROM users;" | grep -v "COUNT")
  log "PASS: Found $USER_COUNT users in users table"
else
  log "WARN: users table not found or empty"
fi

# 8. 清理临时目录
rm -rf $TMP_DIR

log "Binary log recovery completed successfully for database: $DB_NAME"
exit 0

生产环境最佳实践

恢复前准备

  • 制定详细的恢复计划:包括全量恢复步骤、增量恢复顺序、恢复时间点确定方法等
  • 备份当前数据:在恢复前备份当前数据,防止恢复过程中出现问题导致数据进一步丢失
  • 准备回滚方案:制定恢复失败时的回滚方案,确保能够快速恢复到恢复前的状态
  • 通知相关人员:恢复前通知相关业务人员和运维人员,确保恢复过程得到支持

恢复过程中的注意事项

  • 选择合适的时间窗口:选择业务低峰期进行恢复,减少对业务的影响
  • 严格按照顺序恢复:先进行全量恢复,再按照时间顺序应用增量备份
  • 监控恢复过程:实时监控恢复过程,及时发现并解决问题
  • 记录恢复过程:详细记录恢复过程中的每一步操作和结果,便于后续分析

恢复后验证

  • 验证数据完整性:检查恢复后的数据完整性,确保所有数据都已恢复
  • 验证数据一致性:检查恢复后的数据一致性,确保数据之间的关系正确
  • 验证应用程序功能:测试应用程序的核心功能,确保恢复后应用程序能够正常运行
  • 监控数据库性能:恢复后监控数据库性能,确保恢复不会导致性能问题

不同规模数据库的恢复策略

小型数据库(< 10GB)

  • 可以使用基于二进制日志的恢复
  • 恢复时间短,对业务影响小
  • 可以在白天进行恢复

中型数据库(10GB - 100GB)

  • 优先使用基于增量备份的恢复
  • 选择业务低峰期进行恢复
  • 恢复前通知相关人员

大型数据库(> 100GB)

  • 必须使用基于增量备份的恢复
  • 选择夜间或周末进行恢复
  • 提前进行恢复演练
  • 制定详细的恢复计划和回滚方案

云环境增量恢复最佳实践

  • 利用云服务:使用云服务商提供的增量备份恢复功能,如AWS RDS的时间点恢复
  • 选择合适的恢复点:根据RPO要求选择合适的恢复点
  • 验证恢复结果:恢复后验证数据完整性和应用程序功能
  • 监控恢复性能:监控云数据库的恢复性能,优化恢复策略

结论

增量恢复是减少数据丢失、降低RPO的重要手段,掌握增量恢复的方法和最佳实践对于保障数据库的安全性和可靠性至关重要。通过选择合适的恢复方法、制定详细的恢复计划、严格执行恢复步骤和进行充分的恢复后验证,可以确保增量恢复的成功,最大限度地减少数据丢失和业务中断。

在实际生产环境中,应该根据数据库规模、业务需求和技术条件选择合适的增量恢复策略,并定期进行恢复演练,提高应对数据丢失场景的能力。