# MySQL 备份脚本（backup.sh）详解

## 1. 脚本概述

这是一个基于 `xtrabackup` 工具的 MySQL 自动备份脚本，能够根据设定的时间间隔自动执行全量备份或增量备份，并保留指定数量的全量备份文件。

## 2. 脚本功能

- 自动检测是否需要执行全量备份或增量备份
- 支持全量备份和基于全量备份的增量备份
- 自动删除过期的全量备份文件
- 详细的日志记录
- 备份状态检查和错误处理
- 清晰的执行结果输出

## 3. 脚本变量说明

| 变量名 | 含义 | 默认值 |
|--------|------|--------|
| INNOBACKUPEX_PATH | xtrabackup 命令名称 | innobackupex |
| INNOBACKUPEXFULL | xtrabackup 命令完整路径 | /usr/bin/innobackupex |
| MYSQL_CMD | MySQL 连接命令（包含主机、用户名、密码、端口） | --host=localhost --user=root --pCHANGE_ME --pCHANGE_ME |
| MYSQL_UP | MySQL 管理命令（包含用户名、密码、端口） | --user=root --pCHANGE_ME --pCHANGE_ME |
| TMPLOG | 临时日志文件路径 | /tmp/innobackupex.$$.log |
| MY_CNF | MySQL 配置文件路径 | /etc/my.cnf |
| MYSQL | MySQL 客户端命令路径 | /usr/bin/mysql |
| MYSQL_ADMIN | MySQL 管理客户端命令路径 | /usr/bin/mysqladmin |
| BACKUP_DIR | 备份主目录 | /backup |
| FULLBACKUP_DIR | 全量备份目录 | $BACKUP_DIR/full |
| INCRBACKUP_DIR | 增量备份目录 | $BACKUP_DIR/incre |
| FULLBACKUP_INTERVAL | 全量备份间隔（秒） | 86400（24小时） |
| KEEP_FULLBACKUP | 保留全量备份的数量 | 1 |
| logfiledate | 日志文件名（包含日期时间） | backup.`date +%Y%m%d%H%M`.txt |

## 4. 脚本执行流程

1. **环境检查**：
   - 检查 xtrabackup 工具是否安装
   - 检查备份目录是否存在
   - 检查 MySQL 服务是否运行
   - 检查 MySQL 连接凭据是否正确

2. **备份类型判断**：
   - 查找最新的全量备份
   - 根据全量备份的时间间隔判断是否需要执行新的全量备份
   - 如果全量备份未过期，则执行增量备份；否则执行全量备份

3. **执行备份**：
   - 执行全量备份或增量备份
   - 检查备份是否成功
   - 记录备份日志

4. **清理过期备份**：
   - 删除超过保留期限的全量备份

5. **输出结果**：
   - 显示备份完成信息
   - 提示需要保留的备份文件

## 5. 脚本代码详解

```bash
#!/bin/sh  # 指定脚本解释器为 sh
#on xtrabackup 2.2.8  # 脚本基于 xtrabackup 2.2.8 版本开发
# 第一次执行它的时候它会检查是否有完全备份,否则先创建一个全库备份
# 当你再次运行它的时候，它会根据脚本中的设定来基于之前的全备或增量备份进行增量备份

# 定义 xtrabackup 相关变量
INNOBACKUPEX_PATH=innobackupex  # INNOBACKUPEX 的命令名称
INNOBACKUPEXFULL=/usr/bin/$INNOBACKUPEX_PATH  # INNOBACKUPEX 的完整命令路径

# 定义 MySQL 连接相关变量
MYSQL_CMD="--host=localhost --user=root --pCHANGE_ME --pCHANGE_ME"  # MySQL 连接命令
MYSQL_UP=" --user=root --pCHANGE_ME --pCHANGE_ME "  # MySQL 管理命令

# 定义日志和配置文件相关变量
TMPLOG="/tmp/innobackupex.$$.log"  # 临时日志文件，$$ 表示当前进程 ID
MY_CNF=/etc/my.cnf  # MySQL 配置文件路径
MYSQL=/usr/bin/mysql  # MySQL 客户端命令路径
MYSQL_ADMIN=/usr/bin/mysqladmin  # MySQL 管理客户端命令路径

# 定义备份目录相关变量
BACKUP_DIR=/backup  # 备份主目录
FULLBACKUP_DIR=$BACKUP_DIR/full  # 全量备份目录
INCRBACKUP_DIR=$BACKUP_DIR/incre  # 增量备份目录

# 定义备份策略相关变量
FULLBACKUP_INTERVAL=86400  # 全量备份的间隔周期，单位：秒（86400秒=24小时）
KEEP_FULLBACKUP=1  # 至少保留几个全量备份

# 定义日志文件名
logfiledate=backup.`date +%Y%m%d%H%M`.txt  # 日志文件名，包含年月日时分

# 记录脚本开始执行时间
STARTED_TIME=`date +%s`  # 获取当前时间的秒数

#############################################################################
# 显示错误并退出
#############################################################################
error()
{
    echo "$1" 1>&2  # 将错误信息输出到标准错误
    exit 1  # 退出脚本，返回错误码 1
}

# 检查执行环境
# 检查 xtrabackup 工具是否安装或链接到 /usr/bin
if [ ! -x $INNOBACKUPEXFULL ]; then
  error "$INNOBACKUPEXFULL未安装或未链接到/usr/bin."
fi

# 检查备份目录是否存在
if [ ! -d $BACKUP_DIR ]; then
  error "备份目标文件夹:$BACKUP_DIR不存在."
fi

# 检查 MySQL 服务是否运行
mysql_status=`netstat -nl | awk 'NR>2{if ($4 ~ /.*:3306/) {print "Yes";exit 0}}'`

if [ "$mysql_status" != "Yes" ];then
    error "MySQL 没有启动运行."
fi

# 检查 MySQL 连接凭据是否正确
if ! `echo 'exit' | $MYSQL -s $MYSQL_CMD` ; then
 error "提供的数据库用户名或密码不正确!"
fi

# 备份的头部信息

echo "----------------------------"
echo
echo "$0: MySQL备份脚本"
echo "开始于: `date +%F' '%T' '%w`"
echo

# 新建全备和差异备份的目录

mkdir -p $FULLBACKUP_DIR  # 创建全量备份目录，如果已存在则跳过
mkdir -p $INCRBACKUP_DIR  # 创建增量备份目录，如果已存在则跳过

# 查找最新的完全备份
LATEST_FULL_BACKUP=`find $FULLBACKUP_DIR -mindepth 1 -maxdepth 1 -type d -pCHANGE_ME "%P\n" | sort -nr | head -1`

# 查找最近修改的最新备份时间
LATEST_FULL_BACKUP_CREATED_TIME=`stat -c %Y $FULLBACKUP_DIR/$LATEST_FULL_BACKUP`

# 如果全备有效进行增量备份否则执行完全备份
if [ "$LATEST_FULL_BACKUP" -a `expr $LATEST_FULL_BACKUP_CREATED_TIME + $FULLBACKUP_INTERVAL + 5` -ge $STARTED_TIME ] ; then
    # 如果最新的全备未过期则以最新的全备文件名命名在增量备份目录下新建目录
    echo -e "完全备份$LATEST_FULL_BACKUP未过期,将根据$LATEST_FULL_BACKUP名字作为增量备份基础目录名"
    echo "                     "
    NEW_INCRDIR=$INCRBACKUP_DIR/$LATEST_FULL_BACKUP
    mkdir -p $NEW_INCRDIR

    # 查找最新的增量备份是否存在.指定一个备份的路径作为增量备份的基础
    LATEST_INCR_BACKUP=`find $NEW_INCRDIR -mindepth 1 -maxdepth 1 -type d -pCHANGE_ME "%P\n"  | sort -nr | head -1`
        if [ ! $LATEST_INCR_BACKUP ] ; then
            INCRBASEDIR=$FULLBACKUP_DIR/$LATEST_FULL_BACKUP
            echo -e "增量备份将以$INCRBASEDIR作为备份基础目录"
            echo "                     "
        else
            INCRBASEDIR=$INCRBACKUP_DIR/${LATEST_FULL_BACKUP}/${LATEST_INCR_BACKUP}
            echo -e "增量备份将以$INCRBASEDIR作为备份基础目录"
            echo "                     "
        fi

    echo "使用$INCRBASEDIR作为基础本次增量备份的基础目录."
    # 执行增量备份
    $INNOBACKUPEXFULL --defaults-file=$MY_CNF --use-memory=4G $MYSQL_CMD --incremental $NEW_INCRDIR --incremental-basedir $INCRBASEDIR > $TMPLOG 2>&1

    # 保留一份备份的详细日志
    cat $TMPLOG>/backup/$logfiledate

    # 检查备份是否成功
    if [ -z "`tail -1 $TMPLOG | grep 'innobackupex: completed OK!'`" ] ; then
     echo "$INNOBACKUPEX命令执行失败:"; echo
     echo -e "---------- $INNOBACKUPEX_PATH错误 ----------"
     cat $TMPLOG
     rm -f $TMPLOG
     exit 1
    fi

    # 获取备份目录
    THISBACKUP=`awk -- "/Backup created in directory/ { split( \0, p, \"'\" ) ; print p[2] }" $TMPLOG`
    rm -f $TMPLOG

    echo -n "数据库成功备份到:$THISBACKUP"
    echo

    # 提示应该保留的备份文件起点
    LATEST_FULL_BACKUP=`find $FULLBACKUP_DIR -mindepth 1 -maxdepth 1 -type d -pCHANGE_ME "%P\n" | sort -nr | head -1`
    NEW_INCRDIR=$INCRBACKUP_DIR/$LATEST_FULL_BACKUP
    LATEST_INCR_BACKUP=`find $NEW_INCRDIR -mindepth 1 -maxdepth 1 -type d -pCHANGE_ME "%P\n"  | sort -nr | head -1`
    RES_FULL_BACKUP=${FULLBACKUP_DIR}/${LATEST_FULL_BACKUP}
    RES_INCRE_BACKUP=`dirname ${INCRBACKUP_DIR}/${LATEST_FULL_BACKUP}/${LATEST_INCR_BACKUP}`

    echo
    echo -e '\e[31m NOTE:---------------------------------------------------------------------------------.\e[m' # 红色提示
    echo -e "必须保留$KEEP_FULLBACKUP份全备即全备${RES_FULL_BACKUP}和${RES_INCRE_BACKUP}目录中所有增量备份."
    echo -e '\e[31m NOTE:---------------------------------------------------------------------------------.\e[m' # 红色提示
    echo

else
    echo  "*********************************"
    echo -e "正在执行全新的完全备份...请稍等..."
    echo  "*********************************"
    # 执行全量备份
    $INNOBACKUPEXFULL --defaults-file=$MY_CNF  --use-memory=4G  $MYSQL_CMD $FULLBACKUP_DIR > $TMPLOG 2>&1 
    # 保留一份备份的详细日志
    cat $TMPLOG>/backup/$logfiledate

    # 检查备份是否成功
    if [ -z "`tail -1 $TMPLOG | grep 'innobackupex: completed OK!'`" ] ; then
     echo "$INNOBACKUPEX命令执行失败:"; echo
     echo -e "---------- $INNOBACKUPEX_PATH错误 ----------"
     cat $TMPLOG
     rm -f $TMPLOG
     exit 1
    fi

    # 获取备份目录
    THISBACKUP=`awk -- "/Backup created in directory/ { split( \0, p, \"'\" ) ; print p[2] }" $TMPLOG`
    rm -f $TMPLOG

    echo -n "数据库成功备份到:$THISBACKUP"
    echo

    # 提示应该保留的备份文件起点
    LATEST_FULL_BACKUP=`find $FULLBACKUP_DIR -mindepth 1 -maxdepth 1 -type d -pCHANGE_ME "%P\n" | sort -nr | head -1`
    RES_FULL_BACKUP=${FULLBACKUP_DIR}/${LATEST_FULL_BACKUP}

    echo
    echo -e '\e[31m NOTE:---------------------------------------------------------------------------------.\e[m' # 红色提示
    echo -e "无增量备份,必须保留$KEEP_FULLBACKUP份全备即全备${RES_FULL_BACKUP}."
    echo -e '\e[31m NOTE:---------------------------------------------------------------------------------.\e[m' # 红色提示
    echo

fi

# 删除过期的全备

echo -e "find expire backup file...........waiting........."
echo -e "寻找过期的全备文件并删除">>/backup/$logfiledate
# 查找并删除超过 6 天的全量备份
for efile in $(/usr/bin/find $FULLBACKUP_DIR/ -mtime +6)
do
    if [ -d ${efile} ]; then
    rm -rf "${efile}"  # 删除过期的全量备份目录
    echo -e "删除过期全备文件:${efile}" >>/backup/$logfiledate  # 记录删除日志
    elif [ -f ${efile} ]; then
    rm -rf "${efile}"  # 删除过期的全量备份文件
    echo -e "删除过期全备文件:${efile}" >>/backup/$logfiledate  # 记录删除日志
    fi;
    
done

# 检查删除操作是否成功
if [ $? -eq "0" ];then
   echo
   echo -e "未找到可以删除的过期全备文件"
fi

# 输出备份完成时间
echo
echo "完成于: `date +%F' '%T' '%w`"
exit 0  # 脚本正常退出
```

## 6. 执行结果示例

### 6.1 第一次执行（全量备份）

```
----------------------------

./backup.sh: MySQL备份脚本
开始于: 2022-04-03 16:35:11 5


*********************************
正在执行全新的完全备份...请稍等...
*********************************
数据库成功备份到:/backup/full/2022-04-03_16-35-11


 NOTE:---------------------------------------------------------------------------------.
无增量备份,必须保留1份全备即全备/backup/full/2022-04-03_16-35-11.
 NOTE:---------------------------------------------------------------------------------.


find expire backup file...........waiting.........


未找到可以删除的过期全备文件


完成于: 2022-04-03 16:35:25 5
```

### 6.2 第二次执行（增量备份）

```
./backup.sh: MySQL备份脚本
开始于: 2022-04-03 16:35:50 5


完全备份2022-04-03_16-35-11未过期,将根据2022-04-03_16-35-11名字作为增量备份基础目录名
                     
增量备份将以/backup/full/2022-04-03_16-35-11作为备份基础目录
                     
使用/backup/full/2022-04-03_16-35-11作为基础本次增量备份的基础目录.
数据库成功备份到:/backup/incre/2022-04-03_16-35-11/2022-04-03_16-35-50


 NOTE:---------------------------------------------------------------------------------.
必须保留1份全备即全备/backup/full/2022-04-03_16-35-11和/backup/incre/2022-04-03_16-35-11目录中所有增量备份.
 NOTE:---------------------------------------------------------------------------------.


find expire backup file...........waiting.........
```

## 7. 注意事项

1. **xtrabackup 安装**：确保已经安装了 xtrabackup 工具，并且版本与脚本兼容（脚本基于 xtrabackup 2.2.8 开发）。

2. **MySQL 权限**：确保脚本中使用的 MySQL 用户具有足够的权限执行备份操作。

3. **备份目录权限**：确保备份目录具有足够的权限，以便脚本能够写入备份文件和日志。

4. **密码安全性**：脚本中直接明文存储了 MySQL 密码，这存在安全风险。建议使用 MySQL 配置文件或其他安全方式存储密码。

5. **备份策略调整**：根据实际需求调整 `FULLBACKUP_INTERVAL`（全量备份间隔）和 `KEEP_FULLBACKUP`（保留全量备份数量）参数。

6. **日志管理**：脚本会在 `/backup` 目录下生成备份日志，建议定期清理这些日志文件。

7. **增量备份依赖**：增量备份依赖于全量备份，因此请确保不要手动删除正在使用的全量备份。

8. **测试恢复**：定期测试从备份中恢复数据，以确保备份的有效性。

## 8. 适用场景

- 需要定期备份 MySQL 数据库的场景
- 希望自动管理全量备份和增量备份的场景
- 需要保留多个备份版本的场景
- 对数据库可用性要求较高，希望备份过程不影响数据库正常运行的场景（xtrabackup 支持热备份）

## 9. 扩展建议

1. **添加邮件通知**：在备份成功或失败时发送邮件通知。
2. **监控备份状态**：将备份状态集成到监控系统中，以便及时发现备份失败。
3. **备份验证**：添加备份验证步骤，确保备份文件的完整性。
4. **远程备份**：将备份文件复制到远程服务器，提高数据安全性。
5. **加密备份**：对备份文件进行加密，保护敏感数据。

## 10. 总结

这个 MySQL 备份脚本是一个功能完整、自动化程度高的备份解决方案，能够根据设定的策略自动执行全量备份和增量备份，并管理备份文件的保留。通过使用 xtrabackup 工具，它支持热备份，对数据库的影响较小。建议根据实际需求调整脚本中的参数，并定期测试备份的有效性，以确保数据安全。