一、备份脚本
(一)备份脚本
创建了个mysql_backup.sh文件。
bash
#!/bin/bash
# MySQL备份脚本(按日期分文件夹管理)
# 功能:备份指定数据库,排除iplatform库的特定日志表,自动压缩并清理旧备份
#######################################
# 【配置参数区】-- 在此处修改以下参数
#######################################
DB_USER="root" # 数据库用户名
DB_PASS="123456" # 数据库密码(在此处修改)
BACKUP_ROOT_DIR="/home/backup" # 备份根目录(所有日期的备份放在这里)
DB_LIST=( # 需要备份的数据库列表(在此处增删)
"user"
"order"
)
EXCLUDE_TABLES=( # 需要排除的表(格式:库名.表名)
"user.log"
"order.log"
)
RETENTION_DAYS=30 # 备份保留天数(默认30天)
export PATH=$PATH:/usr/local/mysql/bin # 设置环境变量
#######################################
# 【配置结束】-- 以下内容无需修改
#######################################
# 初始化变量(按日期创建子文件夹)
CURRENT_DATE=$(date +%Y%m%d) # 当天日期(用于文件夹命名)
TIMESTAMP=$(date +%Y%m%d_%H%M%S) # 完整时间戳(用于文件名)
BACKUP_DIR="$BACKUP_ROOT_DIR/$CURRENT_DATE" # 当天备份的具体目录
LOG_FILE="$BACKUP_DIR/backup_$TIMESTAMP.log"
# 检查并创建备份目录(包括当天子文件夹)
if [ ! -d "$BACKUP_DIR" ]; then
echo "备份目录 $BACKUP_DIR 不存在,尝试创建..."
mkdir -p "$BACKUP_DIR"
# 检查目录创建结果
if [ $? -ne 0 ]; then
echo "错误:无法创建备份目录 $BACKUP_DIR,请检查权限!"
exit 1
fi
echo "备份目录创建成功"
fi
# 写入日志头部
echo "===== 备份开始于 $TIMESTAMP =====" >> "$LOG_FILE"
echo "备份数据库列表:${DB_LIST[*]}" >> "$LOG_FILE"
echo "排除表列表:${EXCLUDE_TABLES[*]}" >> "$LOG_FILE"
echo "备份文件存放目录:$BACKUP_DIR" >> "$LOG_FILE"
# 备份单个数据库的函数
backup_database() {
local db_name=$1
local exclude_params=""
# 生成排除表参数(仅对iplatform库生效)
if [ "$db_name" = "iplatform" ]; then
for table in "${EXCLUDE_TABLES[@]}"; do
exclude_params+=" --ignore-table=$table"
done
fi
# 执行备份并压缩(文件存放在当天子文件夹中)
echo "开始备份数据库:$db_name" >> "$LOG_FILE"
mysqldump -u"$DB_USER" -p"$DB_PASS" --databases "$db_name" $exclude_params | gzip > "$BACKUP_DIR/${db_name}_$TIMESTAMP.sql.gz"
# 检查备份结果
if [ $? -eq 0 ]; then
echo " 数据库 $db_name 备份成功:${db_name}_$TIMESTAMP.sql.gz" >> "$LOG_FILE"
echo " 数据库 $db_name 备份成功" # 同时输出到控制台
else
echo " 数据库 $db_name 备份失败!" >> "$LOG_FILE"
echo " 数据库 $db_name 备份失败!" # 同时输出到控制台
fi
}
# 批量备份所有数据库
for db in "${DB_LIST[@]}"; do
backup_database "$db"
done
# 清理过期备份(删除整个过期日期的文件夹)
echo "开始清理${RETENTION_DAYS}天前的备份文件夹..." >> "$LOG_FILE"
find "$BACKUP_ROOT_DIR" -maxdepth 1 -type d -name "20[0-9][0-9][0-1][0-9][0-3][0-9]" -mtime +"$RETENTION_DAYS" -exec rm -rf {} \;
echo "清理完成" >> "$LOG_FILE"
# 写入日志尾部
echo "===== 备份结束于 $(date +%Y%m%d_%H%M%S) =====" >> "$LOG_FILE"
echo "" >> "$LOG_FILE"
echo "所有备份操作已完成,详细日志:$LOG_FILE"
(二)远程上传
数据库光完成备份了还不行,放到本地服务器也不可靠,一般还得将备份文件上传到异地服务器进行保存,以防止本地数据库服务器出现问题。
1.前置准备工作
配置SSH免密登录
bash
# 在备份服务器生成密钥
ssh-keygen -t rsa -b 4096
一路回车选默认值

bash
# 复制公钥到远程服务器
ssh-copy-id -p 22 backup_user@192.168.1.100
选yes然后输入远程服务器密码

bash
# 测试连接
ssh -p 22 backup_user@192.168.1.100 "echo '连接成功'"

bash
# 创建必要的目录
mkdir -p /backup
chmod 755 /backup
安装rsync(如果未安装)
bash
# CentOS/RHEL
yum install rsync -y
# Ubuntu/Debian
apt-get install rsync -y
2.上传脚本
bash
#!/bin/bash
# 备份文件上传到远程服务器脚本
# 配合mysql_backup.sh使用,建议在备份完成后执行
#######################################
# 【配置参数区】-- 在此处修改以下参数
#######################################
BACKUP_ROOT_DIR="/home/backup" # 本地备份根目录(与备份脚本相同)
REMOTE_USER="backup_user" # 远程服务器用户名
REMOTE_HOST="192.168.1.100" # 远程服务器IP或域名
REMOTE_PORT="22" # SSH端口(默认22)
REMOTE_BACKUP_DIR="/data/backup" # 远程服务器备份目录
RETENTION_DAYS=30 # 远程保留天数(与本地一致)
LOG_DIR="/var/log/backup" # 上传日志目录
RSYNC_OPTIONS="-avz --delete" # rsync参数:归档、压缩、删除远程多余文件
#######################################
# 【配置结束】-- 以下内容无需修改
#######################################
# 初始化变量
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
UPLOAD_LOG="$LOG_DIR/upload_$TIMESTAMP.log"
CURRENT_DATE=$(date +%Y%m%d)
LOCAL_TODAY_DIR="$BACKUP_ROOT_DIR/$CURRENT_DATE"
# 检查日志目录
mkdir -p "$LOG_DIR"
# 记录开始时间
echo "===== 备份上传开始于 $(date +'%Y-%m-%d %H:%M:%S') =====" > "$UPLOAD_LOG"
# 检查本地今日备份目录是否存在
if [ ! -d "$LOCAL_TODAY_DIR" ]; then
echo "错误:今日备份目录不存在 $LOCAL_TODAY_DIR" | tee -a "$UPLOAD_LOG"
echo "请先运行备份脚本生成备份文件" | tee -a "$UPLOAD_LOG"
exit 1
fi
# 检查本地是否有备份文件
BACKUP_FILES_COUNT=$(find "$LOCAL_TODAY_DIR" -name "*.gz" -type f | wc -l)
if [ "$BACKUP_FILES_COUNT" -eq 0 ]; then
echo "警告:今日备份目录中没有备份文件" | tee -a "$UPLOAD_LOG"
echo "目录:$LOCAL_TODAY_DIR" | tee -a "$UPLOAD_LOG"
fi
# 方法1:使用rsync(推荐,支持断点续传和增量同步)
echo "开始使用rsync同步备份文件到远程服务器..." | tee -a "$UPLOAD_LOG"
echo "远程服务器:$REMOTE_USER@$REMOTE_HOST:$REMOTE_BACKUP_DIR" | tee -a "$UPLOAD_LOG"
rsync $RSYNC_OPTIONS -e "ssh -p $REMOTE_PORT" \
"$BACKUP_ROOT_DIR/" \
"$REMOTE_USER@$REMOTE_HOST:$REMOTE_BACKUP_DIR/" \
2>&1 | tee -a "$UPLOAD_LOG"
RSYNC_EXIT_CODE=${PIPESTATUS[0]}
if [ $RSYNC_EXIT_CODE -eq 0 ]; then
echo "rsync同步成功" | tee -a "$UPLOAD_LOG"
# 清理远程服务器过期备份
echo "开始清理远程服务器过期备份..." | tee -a "$UPLOAD_LOG"
ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" \
"find \"$REMOTE_BACKUP_DIR\" -maxdepth 1 -type d -name '20[0-9][0-9][0-1][0-9][0-3][0-9]' -mtime +$RETENTION_DAYS -exec rm -rf {} \;" \
2>&1 | tee -a "$UPLOAD_LOG"
else
echo "警告:rsync同步部分文件可能失败,退出码: $RSYNC_EXIT_CODE" | tee -a "$UPLOAD_LOG"
echo "可能是网络问题或权限问题" | tee -a "$UPLOAD_LOG"
fi
# 方法2:使用scp(备用方案,每次传输整个文件)
# 如果需要使用scp,可以取消下面代码的注释
# echo "开始使用scp上传今日备份..." | tee -a "$UPLOAD_LOG"
# scp -P "$REMOTE_PORT" -r "$LOCAL_TODAY_DIR" \
# "$REMOTE_USER@$REMOTE_HOST:$REMOTE_BACKUP_DIR/" \
# 2>&1 | tee -a "$UPLOAD_LOG"
# 记录结束时间
echo "===== 备份上传结束于 $(date +'%Y-%m-%d %H:%M:%S') =====" >> "$UPLOAD_LOG"
# 输出总结
echo "" | tee -a "$UPLOAD_LOG"
echo "上传完成总结:" | tee -a "$UPLOAD_LOG"
echo "- 本地备份目录: $BACKUP_ROOT_DIR" | tee -a "$UPLOAD_LOG"
echo "- 远程备份目录: $REMOTE_BACKUP_DIR" | tee -a "$UPLOAD_LOG"
echo "- 上传日志文件: $UPLOAD_LOG" | tee -a "$UPLOAD_LOG"
echo "- 今日备份文件数: $BACKUP_FILES_COUNT" | tee -a "$UPLOAD_LOG"
exit $RSYNC_EXIT_CODE
3.定时任务
bash
# 每天02:00执行备份,02:30执行上传
0 2 * * * /path/to/mysql_backup.sh >> /var/log/backup/cron_backup.log 2>&1
30 2 * * * /path/to/upload_backup.sh >> /var/log/backup/cron_upload.log 2>&1

二、备份恢复
如何恢复数据取决于是怎么备份的。可能需要以下部分或全部步骤。
- 停止 MySQL 服务器。
- 记录服务器的配置和文件权限。
- 将数据从备份中移到 MySQL数据目录
- 改变配置
- 改变文件权限
- 以限制访问模式重启服务器,等待完成启动。
- 载入逻辑备份文件
- 检查和重放二进制日志
- 检测已经还原的数据
- 以完全权限重启服务器
(一)恢复物理备份
物理恢复的核心是文件替换/导入,但需匹配存储引擎特性,否则易导致数据不一致或服务启动失败。
1.MyISAM表的物理恢复
MyISAM表文件(.frm表结构、.MYD数据、.MYI索引)相对独立,但恢复时需保证表无读写操作。
恢复步骤:
- 锁表并刷盘:在目标服务器执行
sql
LOCK TABLES `目标表` READ; -- 禁止表写入
FLUSH TABLES `目标表`; -- 将表数据刷入磁盘
- 替换表文件 :将备份的
.frm/.MYD/.MYI文件复制到目标库的数据目录(如/var/lib/mysql/数据库名/)。 - 解锁表:
sql
UNLOCK TABLES;
关键注意事项:
- 若直接在服务器运行时复制文件(未锁表),可能导致
.MYD/.MYI文件写入不完整,恢复后表数据损坏。 - 恢复后需执行
CHECK TABLE目标表;验证表完整性。
2.InnoDB共享表空间(单文件)恢复
若InnoDB使用共享表空间 (所有表存于ibdata1),恢复需保证文件完整性与一致性。
恢复步骤"
- 关闭MySQL服务:InnoDB共享表空间无法在服务运行时恢复,需执行
bash
systemctl stop mysql
- 替换核心文件 :将备份的
ibdata1(共享表空间)、ib_logfile*(事务日志)、数据库名/下的.frm文件,完整替换到目标服务器的数据目录。
必须同时替换ib_logfile*:InnoDB启动时会校验日志与表空间的LSN(日志序列号),若不匹配则拒绝启动。 - 修正文件权限 :确保文件归属为
mysql用户
bash
chown -R mysql:mysql /var/lib/mysql
- 启动MySQL并验证 :启动服务后执行
CHECK TABLES IN 数据库名;,确认表完整性。
3.InnoDB单表空间(file-per-table)恢复
若启用innodb_file_per_table(每张表对应独立.ibd文件),可恢复单表,但需通过InnoDB的表空间导入/导出机制(原始内容中"只能在原服务器还原"的限制,在MySQL 5.6+中可跨服务器,但需保证表结构一致):
恢复步骤
- 在目标服务器创建空表 :先在目标库中创建与备份表结构完全一致的表(可通过逻辑备份的
CREATE TABLE语句生成)。 - 丢弃空表的表空间:
sql
ALTER TABLE `目标表` DISCARD TABLESPACE;
执行后目标库中会删除该表的.ibd文件。
- 复制备份的
.ibd文件 :将备份的目标表.ibd复制到目标库的数据目录,并修正权限:
bash
chown mysql:mysql /var/lib/mysql/数据库名/目标表.ibd
- 导入表空间:
sql
ALTER TABLE `目标表` IMPORT TABLESPACE;
- 验证表完整性:
sql
CHECK TABLE `目标表`;
关键限制:
- 目标表的结构需与备份表完全一致(字段、类型、索引);
- 若跨服务器恢复,需保证两端InnoDB配置一致(如
innodb_page_size),否则表空间ID不匹配会导致导入失败。
4.物理恢复后的启动前检查:
权限与SELinux检查:
-
除
chown mysql:mysql外,若服务器启用SELinux,需修复文件上下文:restorecon -Rv /var/lib/mysql -
若权限正确但服务仍无法启动,可临时关闭SELinux验证(生产环境需通过
setsebool配置而非永久关闭)。
配置文件匹配 :确保目标服务器的my.cnf(如innodb_buffer_pool_size、innodb_log_file_size)与备份源服务器一致,否则InnoDB可能因参数不匹配启动失败。 -
启动后的错误日志监测 :
启动服务后实时查看错误日志,定位潜在问题:
tail -f /var/log/mysql/error.log- 若InnoDB提示"LSN不匹配",需重新替换完整的
ibdata1与ib_logfile*; - 若提示"表不存在",需检查
.frm文件是否已正确复制。
- 若InnoDB提示"LSN不匹配",需重新替换完整的
(二)逻辑备份恢复
逻辑恢复是通过MySQL执行SQL/文本文件加载数据,需关注效率、错误处理、资源占用。
1.SQL文件的常规加载
基础操作:
直接通过管道加载(推荐,避免临时文件):
bash
# 未压缩文件
mysql -u root -p < 备份文件.sql
# 压缩文件(直接解压+加载,无需单独解压)
gunzip -c 备份文件.sql.gz | mysql -u root -p # gzip压缩
xz -d -c 备份文件.sql.xz | mysql -u root -p # xz压缩
通过SOURCE命令加载(适合远程连接场景):
sql
SET SQL_LOG_BIN = 0; -- 关闭二进制日志,避免恢复操作同步到备库
SOURCE /路径/备份文件.sql;
SET SQL_LOG_BIN = 1;
2.单表恢复(避免"暴力抽取")
若用mydumper导出,默认会按表生成独立文件(如数据库名.表名.sql),恢复时直接加载对应表的文件即可:
bash
mysql -u root -p 数据库名 < 数据库名.目标表.sql
若只有全库SQL文件,可通过以下方式抽取单表(比sed更易懂):
抽取CREATE TABLE语句:
bash
grep -n "CREATE TABLE `目标表`" 备份文件.sql # 找到行号
head -n 行号+10 备份文件.sql | tail -n 20 # 截取表结构(依表结构长度调整)
抽取INSERT语句:
bash
grep "INSERT INTO `目标表`" 备份文件.sql > 目标表_insert.sql
先执行表结构SQL,再加载INSERT文件。
3.恢复中的错误处理
SOURCE命令的错误续跑:默认情况下,SOURCE遇到错误会停止,可通过以下方式忽略错误(仅适用于非关键表):
sql
SET sql_mode = 'NO_ENGINE_SUBSTITUTION';
SOURCE /路径/备份文件.sql --force; # 客户端连接时加--force参数
压缩文件的命名管道加载:若需用SOURCE加载压缩文件,可通过命名管道实现(避免解压):
bash
mkfifo backup_pipe # 创建命名管道
gunzip -c 备份文件.sql.gz > backup_pipe & # 后台解压到管道
mysql -u root -p -e "SOURCE backup_pipe;" # 从管道加载
rm backup_pipe
五、实操建议
1.关键风险规避
- InnoDB 物理备份:禁止直接复制运行中的 .ibd 文件,需使用 XtraBackup 等工具生成一致性备份;恢复后必须执行
innochecksum(文件校验)+CHECK TABLES(数据校验)。 - MyISAM 物理备份:可直接复制 .MYD/.MYI 文件,但需先执行
FLUSH TABLES WITH READ LOCK,避免备份期间数据写入导致文件损坏。 - 跨版本还原:物理备份仅建议在同大版本间还原(如 8.0.20→8.0.30),跨大版本(5.7→8.0)需先通过逻辑备份迁移,避免表空间格式不兼容。
2.实操流程示例
bash
# 1. 每周日执行全量物理备份(XtraBackup)
xtrabackup --user=root --backup --target-dir=/backup/full/$(date +%Y%m%d) --compress
# 2. 每日执行增量物理备份(基于前一次全量/增量备份)
xtrabackup --user=root --backup --target-dir=/backup/inc/$(date +%Y%m%d) --incremental-basedir=/backup/full/20240512 --compress
# 3. 每月1日将物理备份还原到备用服务器
xtrabackup --prepare --target-dir=/backup/full/20240512
cp -r /backup/full/20240512 /data/mysql
chown -R mysql:mysql /data/mysql
systemctl start mysql
# 4. 备用服务器执行逻辑备份(归档用)
mysqldump --user=root --all-databases --single-transaction --quick --compress --output=/backup/logical/20240501_all.sql.gz
# 5. 验证逻辑备份有效性
mysql --user=root -e "SOURCE /backup/logical/20240501_all.sql.gz" test_db
mysqlcheck -u root -p --all-databases