#!/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 "$@"