diff --git a/full_backup.sh b/full_backup.sh new file mode 100644 index 0000000..a558e20 --- /dev/null +++ b/full_backup.sh @@ -0,0 +1,447 @@ +#!/bin/bash +# ============================================================================ +# MySQL 8.0.24 全量备份脚本 +# ============================================================================ +# 作者: AI Assistant +# 版本: 1.0.0 +# 说明: 使用 mysqldump 进行全量逻辑备份 +# +# 功能特点: +# - 支持单数据库或所有数据库备份 +# - 记录 binlog 位置用于后续增量备份 +# - 支持压缩备份 +# - 自动清理过期备份 +# - 完善的错误处理和日志记录 +# +# 使用方法: +# ./full_backup.sh [选项] +# +# 选项: +# -d, --database 指定要备份的数据库 (可多次使用) +# -c, --compress 启用压缩 (默认: 是) +# -n, --no-compress 禁用压缩 +# -h, --help 显示帮助信息 +# +# 示例: +# ./full_backup.sh # 备份所有数据库 +# ./full_backup.sh -d mydb # 仅备份 mydb 数据库 +# ./full_backup.sh -d db1 -d db2 # 备份多个指定数据库 +# ============================================================================ + +set -o pipefail # 管道命令中任何一个失败都会导致整个管道失败 + +# ---------------------------------------------------------------------------- +# 脚本路径和配置加载 +# ---------------------------------------------------------------------------- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# 加载配置文件 +if [[ ! -f "${SCRIPT_DIR}/config.sh" ]]; then + echo "[ERROR] 配置文件不存在: ${SCRIPT_DIR}/config.sh" + exit 1 +fi +source "${SCRIPT_DIR}/config.sh" + +# 加载公共函数库 +if [[ ! -f "${SCRIPT_DIR}/lib/common.sh" ]]; then + echo "[ERROR] 公共函数库不存在: ${SCRIPT_DIR}/lib/common.sh" + exit 1 +fi +source "${SCRIPT_DIR}/lib/common.sh" + +# ---------------------------------------------------------------------------- +# 全局变量 +# ---------------------------------------------------------------------------- +BACKUP_TIMESTAMP=$(date +"${TIMESTAMP_FORMAT}") +BACKUP_NAME="${BACKUP_PREFIX}_full_${BACKUP_TIMESTAMP}" +BACKUP_DIR="${FULL_BACKUP_DIR}/${BACKUP_NAME}" +LOCK_FILE="${BACKUP_ROOT_DIR}/.full_backup.lock" +LOG_FILE="${LOG_DIR}/full_backup_${BACKUP_TIMESTAMP}.log" +ENABLE_COMPRESS=true +SPECIFIED_DATABASES=() + +# ---------------------------------------------------------------------------- +# 显示帮助信息 +# ---------------------------------------------------------------------------- +show_help() { + cat << EOF +MySQL 8.0.24 全量备份脚本 + +使用方法: + $(basename "$0") [选项] + +选项: + -d, --database 指定要备份的数据库 (可多次使用) + -c, --compress 启用压缩 (默认: 是) + -n, --no-compress 禁用压缩 + -h, --help 显示此帮助信息 + +示例: + $(basename "$0") # 备份所有数据库 + $(basename "$0") -d mydb # 仅备份 mydb 数据库 + $(basename "$0") -d db1 -d db2 # 备份多个指定数据库 + $(basename "$0") --no-compress # 不压缩备份 + +环境要求: + - MySQL 8.0.24 + - mysqldump 工具 + - 足够的磁盘空间 + - MySQL 用户需要 RELOAD, LOCK TABLES, REPLICATION CLIENT 权限 +EOF +} + +# ---------------------------------------------------------------------------- +# 解析命令行参数 +# ---------------------------------------------------------------------------- +parse_args() { + while [[ $# -gt 0 ]]; do + case "$1" in + -d|--database) + shift + if [[ -z "$1" ]]; then + die "选项 -d/--database 需要一个参数" + fi + SPECIFIED_DATABASES+=("$1") + shift + ;; + -c|--compress) + ENABLE_COMPRESS=true + shift + ;; + -n|--no-compress) + ENABLE_COMPRESS=false + shift + ;; + -h|--help) + show_help + exit 0 + ;; + *) + die "未知选项: $1\n使用 --help 查看帮助" + ;; + esac + done +} + +# ---------------------------------------------------------------------------- +# 初始化备份环境 +# ---------------------------------------------------------------------------- +init_backup() { + log_info "============================================================" + log_info "MySQL 全量备份开始" + log_info "============================================================" + log_info "备份时间: $BACKUP_TIMESTAMP" + log_info "备份目录: $BACKUP_DIR" + log_info "日志文件: $LOG_FILE" + log_info "============================================================" + + # 设置错误处理 + setup_error_trap + + # 检查必要命令 + check_commands "$MYSQLDUMP_PATH" "$MYSQL_PATH" "gzip" + + # 检查 MySQL 连接 + check_mysql_connection + + # 创建必要目录 + ensure_dir "$BACKUP_DIR" + ensure_dir "$LOG_DIR" + + # 检查磁盘空间 (预估需要 5GB) + check_disk_space "$BACKUP_DIR" 5120 + + # 获取锁 + acquire_lock "$LOCK_FILE" "全量备份" + + # 设置清理 trap + trap cleanup EXIT +} + +# ---------------------------------------------------------------------------- +# 清理函数 +# ---------------------------------------------------------------------------- +cleanup() { + local exit_code=$? + + # 释放锁 + release_lock "$LOCK_FILE" + + if [[ $exit_code -ne 0 ]]; then + log_error "备份过程中发生错误,退出码: $exit_code" + + # 清理不完整的备份 + if [[ -d "$BACKUP_DIR" ]]; then + log_warn "清理不完整的备份目录: $BACKUP_DIR" + rm -rf "$BACKUP_DIR" + fi + + send_notification "[失败] MySQL 全量备份" "备份失败,请检查日志: $LOG_FILE" + fi +} + +# ---------------------------------------------------------------------------- +# 获取要备份的数据库列表 +# ---------------------------------------------------------------------------- +get_backup_databases() { + if [[ ${#SPECIFIED_DATABASES[@]} -gt 0 ]]; then + # 使用命令行指定的数据库 + echo "${SPECIFIED_DATABASES[@]}" + elif [[ -n "$DATABASES_TO_BACKUP" ]]; then + # 使用配置文件中指定的数据库 + echo "$DATABASES_TO_BACKUP" + else + # 备份所有数据库 (排除系统数据库) + get_databases + fi +} + +# ---------------------------------------------------------------------------- +# 执行数据库备份 +# ---------------------------------------------------------------------------- +backup_database() { + local db_name="$1" + local output_file="${BACKUP_DIR}/${db_name}.sql" + + log_info "备份数据库: $db_name" + + # 构建 mysqldump 命令 + # --single-transaction: 使用事务保证一致性 (InnoDB) + # --master-data=2: 将 binlog 位置信息以注释形式写入 + # --routines: 包含存储过程和函数 + # --triggers: 包含触发器 + # --events: 包含事件调度器 + # --set-gtid-purged=OFF: 避免 GTID 相关问题 + local dump_cmd="$MYSQLDUMP_PATH" + local dump_args="-h${MYSQL_HOST} -P${MYSQL_PORT} -u${MYSQL_USER}" + + if [[ -n "$MYSQL_PASSWORD" ]]; then + dump_args="$dump_args -p${MYSQL_PASSWORD}" + fi + + dump_args="$dump_args --single-transaction --master-data=2" + dump_args="$dump_args --routines --triggers --events" + dump_args="$dump_args --set-gtid-purged=OFF" + dump_args="$dump_args --hex-blob" # 二进制数据使用十六进制格式 + dump_args="$dump_args --quick" # 不缓存查询结果,节省内存 + dump_args="$dump_args --lock-tables=false" # 使用 single-transaction 时不需要锁表 + + # 执行备份 + log_debug "执行命令: $dump_cmd $dump_args $db_name" + + if ! $dump_cmd $dump_args "$db_name" > "$output_file" 2>> "$LOG_FILE"; then + die "数据库 $db_name 备份失败" + fi + + # 压缩备份文件 + if [[ "$ENABLE_COMPRESS" == "true" ]]; then + log_info "压缩备份文件: ${output_file}" + gzip -f "$output_file" || die "压缩失败: $output_file" + output_file="${output_file}.gz" + fi + + local file_size=$(get_file_size "$output_file") + log_info "数据库 $db_name 备份完成,大小: $file_size" +} + +# ---------------------------------------------------------------------------- +# 备份所有数据库 (使用 --all-databases) +# ---------------------------------------------------------------------------- +backup_all_databases() { + local output_file="${BACKUP_DIR}/all_databases.sql" + + log_info "备份所有数据库" + + local dump_cmd="$MYSQLDUMP_PATH" + local dump_args="-h${MYSQL_HOST} -P${MYSQL_PORT} -u${MYSQL_USER}" + + if [[ -n "$MYSQL_PASSWORD" ]]; then + dump_args="$dump_args -p${MYSQL_PASSWORD}" + fi + + dump_args="$dump_args --all-databases" + dump_args="$dump_args --single-transaction --master-data=2" + dump_args="$dump_args --routines --triggers --events" + dump_args="$dump_args --set-gtid-purged=OFF" + dump_args="$dump_args --hex-blob --quick" + dump_args="$dump_args --lock-tables=false" + + log_debug "执行命令: $dump_cmd $dump_args" + + if ! $dump_cmd $dump_args > "$output_file" 2>> "$LOG_FILE"; then + die "全库备份失败" + fi + + # 压缩备份文件 + if [[ "$ENABLE_COMPRESS" == "true" ]]; then + log_info "压缩备份文件: ${output_file}" + gzip -f "$output_file" || die "压缩失败: $output_file" + output_file="${output_file}.gz" + fi + + local file_size=$(get_file_size "$output_file") + log_info "全库备份完成,大小: $file_size" +} + +# ---------------------------------------------------------------------------- +# 记录 binlog 位置信息 +# ---------------------------------------------------------------------------- +save_binlog_position() { + local binlog_info_file="${BACKUP_DIR}/binlog_position.txt" + + log_info "记录 binlog 位置信息" + + local binlog_position=$(get_binlog_position) + local binlog_file=$(echo "$binlog_position" | cut -d: -f1) + local binlog_pos=$(echo "$binlog_position" | cut -d: -f2) + + cat > "$binlog_info_file" << EOF +# MySQL Binlog Position +# 备份时间: $BACKUP_TIMESTAMP +# 用于增量备份的起始位置 + +BINLOG_FILE=$binlog_file +BINLOG_POSITION=$binlog_pos +EOF + + log_info "Binlog 位置: $binlog_file:$binlog_pos" +} + +# ---------------------------------------------------------------------------- +# 保存备份元数据 +# ---------------------------------------------------------------------------- +save_metadata() { + local metadata_file="${BACKUP_DIR}/metadata.txt" + + log_info "保存备份元数据" + + local end_time=$(date +%s) + local duration=$(calculate_duration "$START_TIME" "$end_time") + local backup_size=$(get_file_size "$BACKUP_DIR") + + cat > "$metadata_file" << EOF +# MySQL 全量备份元数据 +# ======================================== + +备份类型: 全量备份 (Full Backup) +备份时间: $BACKUP_TIMESTAMP +备份目录: $BACKUP_NAME +备份大小: $backup_size +执行耗时: $duration + +MySQL 服务器: ${MYSQL_HOST}:${MYSQL_PORT} +MySQL 用户: $MYSQL_USER + +备份选项: +- 压缩: $ENABLE_COMPRESS +- 压缩工具: $COMPRESS_TOOL + +备份的数据库: +$(get_backup_databases | tr ' ' '\n' | sed 's/^/- /') + +备份状态: 成功 +EOF + + log_info "元数据已保存" +} + +# ---------------------------------------------------------------------------- +# 验证备份完整性 +# ---------------------------------------------------------------------------- +verify_backup() { + log_info "验证备份完整性" + + local backup_files=$(find "$BACKUP_DIR" -name "*.sql*" -type f) + + if [[ -z "$backup_files" ]]; then + die "备份验证失败: 没有找到备份文件" + fi + + # 检查每个备份文件 + while IFS= read -r file; do + local file_size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null) + + if [[ $file_size -eq 0 ]]; then + die "备份验证失败: 文件为空 - $file" + fi + + # 如果是压缩文件,验证压缩完整性 + if [[ "$file" == *.gz ]]; then + if ! gzip -t "$file" 2>/dev/null; then + die "备份验证失败: 压缩文件损坏 - $file" + fi + fi + + log_debug "文件验证通过: $file ($(get_file_size $file))" + done <<< "$backup_files" + + log_info "备份验证通过" +} + +# ---------------------------------------------------------------------------- +# 清理过期备份 +# ---------------------------------------------------------------------------- +cleanup_expired_backups() { + log_info "检查过期备份..." + cleanup_old_backups "$FULL_BACKUP_DIR" "$FULL_BACKUP_RETENTION_DAYS" +} + +# ---------------------------------------------------------------------------- +# 主函数 +# ---------------------------------------------------------------------------- +main() { + # 记录开始时间 + START_TIME=$(date +%s) + + # 解析参数 + parse_args "$@" + + # 初始化 + init_backup + + # 记录 binlog 位置 (在备份开始时) + save_binlog_position + + # 获取要备份的数据库 + local databases=$(get_backup_databases) + + if [[ ${#SPECIFIED_DATABASES[@]} -gt 0 ]] || [[ -n "$DATABASES_TO_BACKUP" ]]; then + # 分别备份每个数据库 + for db in $databases; do + backup_database "$db" + done + else + # 备份所有数据库 + backup_all_databases + fi + + # 验证备份 + verify_backup + + # 保存元数据 + save_metadata + + # 清理过期备份 + cleanup_expired_backups + + # 计算总耗时 + local end_time=$(date +%s) + local duration=$(calculate_duration "$START_TIME" "$end_time") + local backup_size=$(get_file_size "$BACKUP_DIR") + + log_info "============================================================" + log_info "MySQL 全量备份完成" + log_info "============================================================" + log_info "备份目录: $BACKUP_DIR" + log_info "备份大小: $backup_size" + log_info "执行耗时: $duration" + log_info "============================================================" + + # 发送成功通知 + send_notification "[成功] MySQL 全量备份" "备份完成\n目录: $BACKUP_DIR\n大小: $backup_size\n耗时: $duration" + + return 0 +} + +# 执行主函数 +main "$@"