MySQL 数据库同步指南

本文提供两个 Bash 脚本,用于在本地和远程服务器之间同步 MySQL 数据库,支持本机运行的 MySQL(native)和 Docker 容器中的 MySQL(docker):

  1. mysql_remote_to_local_sync.sh:将远程服务器的 MySQL 数据库同步到本地。
  2. mysql_local_to_remote_sync.sh:将本地 MySQL 数据库同步到远程服务器。

脚本特点:

  • 支持从 .env 文件加载配置,定义默认变量。
  • 在运行前显示配置信息,要求用户确认。
  • 支持 native 模式的自定义 MySQL 端口(默认 3306)。
  • 日志以中文输出到控制台,敏感信息(如密码)使用占位符。
  • 检查必要命令(如 mysqlmysqldumpdockerssh)和数据库连接。
  • 自动备份数据库,清理旧备份文件(保留最近 5 个)。

环境假设

  • 远程服务器
    • IP:<IP>
    • SSH 用户:<USER>
    • MySQL 类型:<REMOTE_MYSQL_TYPE>nativedocker
    • 端口:<REMOTE_MYSQL_PORT>native 模式,默认 3306)
    • 容器名(docker 模式):<SOURCE_CONTAINER><TARGET_CONTAINER>
    • 数据库:<SOURCE_DB><TARGET_DB>
    • 用户:<SOURCE_USER><TARGET_USER>
    • 密码:<PASSWORD>
  • 本地主机
    • MySQL 类型:<LOCAL_MYSQL_TYPE>dockernative
    • 端口:<LOCAL_MYSQL_PORT>native 模式,默认 3306) - 容器名(docker 模式):<SOURCE><DATABASE> - 数据库:<SOURCE><DATABASE> - 用户:<USER><TARGET_USER>
    • 密码:<PASSWORD>
  • 备份目录<BACKUP_DIR>
  • 要求
    • 本地和远程主机安装 dockerssh(若使用 docker),或 mysqlmysqldump(若使用 native)。
    • SSH 配置免密登录。
    • 可选:.env 文件覆盖默认配置。

脚本功能

1. 远程到本地同步 (mysql_remote_to_local_sync.sh)

  • 功能

    • 从远程 MySQL(nativedocker)备份数据库 <SOURCE_DB>
    • 将备份文件传输到本地,导入本地 MySQL(nativedocker)。
    • 清理旧备份文件(保留最近 5 个)。
    • 支持 .env 文件配置、默认变量、用户确认。
    • 日志以中文记录,包括 LOCAL_MYSQL_TYPEREMOTE_MYSQL_TYPE 检查。
  • 流程

    1. 加载 .env 文件,应用默认变量。
    2. 显示配置信息,要求用户输入 y/yes 确认。
    3. 检查命令(sshdockermysqlmysqldump)。
    4. 验证远程和本地数据库连接。
    5. 备份远程数据库到本地 <BACKUP_DIR>
    6. 导入备份到本地目标数据库。
    7. 清理旧备份文件。

2. 本地到远程同步 (mysql_local_to_remote_sync.sh)

  • 功能

    • 从本地 MySQL(nativedocker)备份数据库 <SOURCE_DB>
    • 将备份文件传输到远程服务器,导入远程 MySQL(nativedocker)。
    • 清理本地旧备份文件(保留最近 5 个)。
    • 支持 .env 文件配置、默认变量、用户确认。
    • 日志以中文记录,包括 LOCAL_MYSQL_TYPEREMOTE_MYSQL_TYPE 检查。
  • 流程

    1. 加载 .env 文件,应用默认变量。
    2. 显示配置信息,要求用户输入 y/yes 确认。
    3. 检查命令(sshdockermysqlmysqldump)。
    4. 验证本地和远程数据库连接。
    5. 备份本地数据库到 <BACKUP_DIR>
    6. 传输备份文件并导入远程目标数据库。
    7. 清理旧备份文件。

配置步骤

1. 创建 .env 文件

在脚本所在目录创建 .env 文件,定义配置变量。示例:

REMOTE_USER=user
REMOTE_HOST=192.168.1.100
REMOTE_MYSQL_TYPE=docker
REMOTE_MYSQL_PORT=3306
SOURCE_CONTAINER=remote_mysql
SOURCE_USER=db_user
SOURCE_PASS=db_pass
SOURCE_DB=mydb
LOCAL_MYSQL_TYPE=docker
LOCAL_MYSQL_PORT=3306
TARGET_CONTAINER=local_mysql
TARGET_USER=db_user
TARGET_PASS=db_pass
TARGET_DB=localdb
BACKUP_DIR=/backup/mysql
INTERACTIVE_CONFIRMATION=true
  • 默认变量(若 .env 未定义):

    DEFAULT_REMOTE_USER=<USER>
    DEFAULT_REMOTE_HOST=<IP>
    DEFAULT_REMOTE_MYSQL_TYPE=docker
    DEFAULT_REMOTE_MYSQL_PORT=3306
    DEFAULT_SOURCE_CONTAINER=<SOURCE_CONTAINER>
    DEFAULT_SOURCE_USER=<SOURCE_USER>
    DEFAULT_SOURCE_PASS=<SOURCE_PASS>
    DEFAULT_SOURCE_DB=<SOURCE_DB>
    DEFAULT_LOCAL_MYSQL_TYPE=docker
    DEFAULT_LOCAL_MYSQL_PORT=3306
    DEFAULT_TARGET_CONTAINER=<TARGET_CONTAINER>
    DEFAULT_TARGET_USER=<TARGET_USER>
    DEFAULT_TARGET_PASS=<TARGET_PASS>
    DEFAULT_TARGET_DB=<TARGET_DB>
    DEFAULT_BACKUP_DIR=<BACKUP_DIR>
    DEFAULT_INTERACTIVE_CONFIRMATION=true
    
  • 设置权限

    chmod 600 .env
    

2. 本地主机准备

  • 安装依赖

    • Docker:
      sudo apt-get update && sudo apt-get install -y docker.io openssh-client  # Debian/Ubuntu
      sudo yum install -y docker && sudo systemctl start docker && systemctl enable docker  # CentOS/RHEL
      
    • 本机 MySQL:
      sudo apt-get update && sudo apt-get install -y mysql-client  # Debian/Ubuntu
      sudo yum install -y mysql  # CentOS/RHEL
      
  • 启动本地 MySQL

    • Docker
      docker run -d \
        --name local_mysql \
        -p 3306:3306 \
        -e MYSQL_ROOT_PASSWORD=root_pass \
        -e MYSQL_DATABASE=localdb \
        mysql:latest
      
    • 本机
      sudo systemctl start mysql
      
      配置用户:
      GRANT ALL PRIVILEGES ON localdb.* TO 'db_user'@'localhost' IDENTIFIED BY 'db_pass';
      FLUSH PRIVILEGES;
      
      非默认端口(如 3307):
      sudo vim /etc/mysql/my.cnf
      
      修改 [mysqld]
      port = 3307
      
      重启:
      sudo systemctl restart mysql
      
  • 创建备份目录

    mkdir -p /backup/mysql
    chmod 755 /backup/mysql
    

3. 远程服务器准备

  • 安装依赖

    • Docker:
      ssh user@192.168.1.100 "sudo apt-get update && sudo apt-get install -y docker.io"
      
    • 本机 MySQL:
      ssh user@192.168.1.100 "sudo apt-get update && sudo apt-get install -y mysql-server"
      
  • 启动远程 MySQL

    • Docker
      ssh user@192.168.1.100 docker run -d \
        --name remote_mysql \
        -p 3306:3306 \
        -e MYSQL_ROOT_PASSWORD=root_pass \
        -e MYSQL_DATABASE=mydb \
        mysql:latest
      
    • 本机
      ssh user@192.168.1.100 "sudo systemctl start mysql"
      
      配置用户:
      GRANT ALL PRIVILEGES ON mydb.* TO 'db_user'@'%' IDENTIFIED BY 'db_pass';
      FLUSH PRIVILEGES;
      
      非默认端口(如 3307),修改 /etc/mysql/my.cnf 并重启。
  • 配置 SSH 免密登录

    ssh-keygen
    ssh-copy-id user@192.168.1.100
    

4. 测试连接

  • 本地数据库

    • Docker:
      docker exec local_mysql mysql -u db_user --password=db_pass -e "SELECT 1"
      
    • 本机:
      mysql -u db_user --password=db_pass -P 3306 -e "SELECT 1"
      
  • 远程数据库

    • Docker:
      ssh user@192.168.1.100 "docker exec remote_mysql mysql -u db_user --password=db_pass -e 'SELECT 1'"
      
    • 本机:
      ssh user@192.168.1.100 "mysql -u db_user --password=db_pass -P 3306 -e 'SELECT 1'"
      

使用方法

  1. 保存脚本

    • 远程到本地:mysql_remote_to_local_sync.sh
    • 本地到远程:mysql_local_to_remote_sync.sh
    • 添加权限:
      chmod +x mysql_remote_to_local_sync.sh mysql_local_to_remote_sync.sh
      
  2. 配置

    • 优先级.env 文件 > 环境变量 > 默认变量。
    • 创建 .env 文件,或修改脚本中的 DEFAULT_* 变量。
    • 确保 <BACKUP_DIR> 存在且可写。
  3. 运行脚本

    • 远程到本地:
      ./mysql_remote_to_local_sync.sh
      
    • 本地到远程:
      ./mysql_local_to_remote_sync.sh
      
    • 脚本显示配置并提示确认:
      >==============<
      |远程服务器信息|
      SSH用户名: user
      远程服务器IP: 192.168.1.100
      ...
      请核实配置是否一致? [y/N]
      
    • 输入 yyes 继续,否则退出。
  4. 预期输出

    [2025-06-04 19:51:00] 远程 mydb 数据库同步至本地 localdb 数据库
    [2025-06-04 19:51:00] 开始数据库同步流程...
    [2025-06-04 19:51:00] 正在检查 LOCAL_MYSQL_TYPE REMOTE_MYSQL_TYPE ..
    [2025-06-04 19:51:00] 正在检查 LOCAL_MYSQL_TYPE:docker ..
    [2025-06-04 19:51:00] 正在检查 REMOTE_MYSQL_TYPE:docker ..
    [2025-06-04 19:51:01] 源数据库连接成功
    [2025-06-04 19:51:02] 备份成功完成: /backup/mysql/mydb_20250604_195100.sql
    [2025-06-04 19:51:03] 同步成功完成
    [2025-06-04 19:51:03] 已清理旧备份文件
    [2025-06-04 19:51:03] 数据库同步成功完成
    

脚本代码

1. mysql_remote_to_local_sync.sh

#!/bin/bash

# 默认配置
DEFAULT_REMOTE_USER="<USER>"                     # SSH 用户名
DEFAULT_REMOTE_HOST="<IP>"                       # 远程服务器 IP
DEFAULT_REMOTE_MYSQL_TYPE="docker"               # 远程 MySQL 类型: native 或 docker
DEFAULT_REMOTE_MYSQL_PORT=3306                   # 远程 MySQL 端口(仅 native 模式,默认 3306)
DEFAULT_SOURCE_CONTAINER="<SOURCE_CONTAINER>"     # 远程 Docker 容器名
DEFAULT_SOURCE_USER="<SOURCE_USER>"              # 源数据库用户名
DEFAULT_SOURCE_PASS="<SOURCE_PASS>"              # 源数据库密码
DEFAULT_SOURCE_DB="<SOURCE_DB>"                  # 源数据库名
DEFAULT_LOCAL_MYSQL_TYPE="docker"                # 本地 MySQL 类型: native 或 docker
DEFAULT_LOCAL_MYSQL_PORT=3306                    # 本地 MySQL 端口(仅 native 模式,默认 3306)
DEFAULT_TARGET_CONTAINER="<TARGET_CONTAINER>"     # 本地 Docker 容器名
DEFAULT_TARGET_USER="<TARGET_USER>"              # 目标数据库用户名
DEFAULT_TARGET_PASS="<TARGET_PASS>"              # 目标数据库密码
DEFAULT_TARGET_DB="<TARGET_DB>"                  # 目标数据库名
DEFAULT_BACKUP_DIR="<BACKUP_DIR>"                # 备份目录
DEFAULT_INTERACTIVE_CONFIRMATION=true            #是否开启交互确认
# 加载现有 .env 文件
if [ -f ".env" ]; then
  export $(cat .env | grep -v '^#' | xargs)
fi

# 远程服务器信息
REMOTE_USER=${REMOTE_USER:-$DEFAULT_REMOTE_USER}
REMOTE_HOST=${REMOTE_HOST:-$DEFAULT_REMOTE_HOST}
REMOTE_MYSQL_TYPE=${REMOTE_MYSQL_TYPE:-$DEFAULT_REMOTE_MYSQL_TYPE}
REMOTE_MYSQL_PORT=${REMOTE_MYSQL_PORT:-$DEFAULT_REMOTE_MYSQL_PORT}
SOURCE_CONTAINER=${SOURCE_CONTAINER:-$DEFAULT_SOURCE_CONTAINER}
SOURCE_USER=${SOURCE_USER:-$DEFAULT_SOURCE_USER}
SOURCE_PASS=${SOURCE_PASS:-$DEFAULT_SOURCE_PASS}
SOURCE_DB=${SOURCE_DB:-$DEFAULT_SOURCE_DB}

# 本地目标数据库信息
LOCAL_MYSQL_TYPE=${LOCAL_MYSQL_TYPE:-$DEFAULT_LOCAL_MYSQL_TYPE}
LOCAL_MYSQL_PORT=${LOCAL_MYSQL_PORT:-$DEFAULT_LOCAL_MYSQL_PORT}
TARGET_CONTAINER=${TARGET_CONTAINER:-$DEFAULT_TARGET_CONTAINER}
TARGET_USER=${TARGET_USER:-$DEFAULT_TARGET_USER}
TARGET_PASS=${TARGET_PASS:-$DEFAULT_TARGET_PASS}
TARGET_DB=${TARGET_DB:-$DEFAULT_TARGET_DB}

# 备份设置
BACKUP_DIR=${BACKUP_DIR:-$DEFAULT_BACKUP_DIR}
#是否开启交互确认
INTERACTIVE_CONFIRMATION=${INTERACTIVE_CONFIRMATION:-$DEFAULT_INTERACTIVE_CONFIRMATION} 
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

OPEN_LOG=true

SH_INTERACTIVE_CONFIRMATION=$1
SH_OPEN_LOG=$2

# 日志记录函数
log() {
#前景色(字体颜色):黑色(BK):30|红色(RD):31|绿色(GN):32|
#黄色(YW):33|蓝色(BE):34|紫色(PE):35|青色(CN):36|白色(WE):37 
    color=36
    if [ "$2" != "" ];then
        read_color="$2"
        if [ "$read_color" == "BK" ]; then
            color=30
        elif [ "$read_color" == "RD" ]; then
            color=31
        elif [ "$read_color" == "GN" ]; then
            color=32
        elif [ "$read_color" == "YW" ]; then
            color=33
        elif [ "$read_color" == "BE" ]; then
            color=34
        elif [ "$read_color" == "PE" ]; then
            color=35
        elif [ "$read_color" == "WE" ]; then
            color=36
        else
            color=37
        fi
    fi
    echo -e "\033[${color}m[$(date '+%Y-%m-%d %H:%M:%S')] $1\033[0m"
    #echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

log "SH_INTERACTIVE_CONFIRMATION:$SH_INTERACTIVE_CONFIRMATION"
log "SH_OPEN_LOG:$SH_OPEN_LOG"
if [ "$SH_INTERACTIVE_CONFIRMATION" != "" ]; then
INTERACTIVE_CONFIRMATION=$SH_INTERACTIVE_CONFIRMATION
fi
if [ "$SH_OPEN_LOG" != "" ]; then
OPEN_LOG=$SH_OPEN_LOG
fi

#======================================================================================#
check_env() {
log "
>==============<
|远程服务器信息|
>==============<
SSH用户名:
REMOTE_USER=$REMOTE_USER                  
远程服务器IP:                                
REMOTE_HOST=$REMOTE_HOST                  
远程 MySQL类型(native或 docker):
REMOTE_MYSQL_TYPE=$REMOTE_MYSQL_TYPE      
远程 MySQL端口(仅 native模式,默认3306):
REMOTE_MYSQL_PORT=$REMOTE_MYSQL_PORT 
远程 Docker容器名:
SOURCE_CONTAINER=$SOURCE_CONTAINER       
源数据库用户名:     
SOURCE_USER=$SOURCE_USER                 
源数据库密码:    
SOURCE_PASS=$SOURCE_PASS                 
源数据库名:
SOURCE_DB=$SOURCE_DB                      
>===================<
|本地目标数据库信息|
>===================<
本地 MySQL类型(native或 docker):
LOCAL_MYSQL_TYPE=$LOCAL_MYSQL_TYPE       
本地 MySQL端口(仅 native模式,默认3306):
LOCAL_MYSQL_PORT=$LOCAL_MYSQL_PORT     
本地 Docker容器名:
TARGET_CONTAINER=$TARGET_CONTAINER        
目标数据库用户名:
TARGET_USER=$TARGET_USER                  
目标数据库密码:
TARGET_PASS=$TARGET_PASS                 
目标数据库名:
TARGET_DB=$TARGET_DB                     
备份设置:
BACKUP_DIR=$BACKUP_DIR"
      if [ "$INTERACTIVE_CONFIRMATION" == "true" ]; then
          log "交互确认已开启"
          read -r -p "[$(date '+%Y-%m-%d %H:%M:%S')] 请核实配置是否一致? [y/N] " key
          if [[ "${key,,}" =~ ^(yes|y)$ ]]
          then
              echo ""
          else
              exit 0
          fi
      else
          log "交互确认已关闭"
      fi
}

# 检查必要的命令
check_required_commands() {
    log "正在检查 LOCAL_MYSQL_TYPE REMOTE_MYSQL_TYPE .."
    command -v ssh >/dev/null 2>&1 || { echo "需要安装 ssh,但未找到。程序中止。" >&2; exit 1; }
    case "$LOCAL_MYSQL_TYPE" in
        "docker")
            log "正在检查 LOCAL_MYSQL_TYPE:$LOCAL_MYSQL_TYPE .."
            command -v docker >/dev/null 2>&1 || { echo "本地 MySQL 类型为 docker,但未找到 docker 命令。程序中止。" >&2; exit 1; }
            docker info >/dev/null 2>&1 || { echo "本地 Docker 服务未运行。程序中止。" >&2; exit 1; }
            docker exec "$TARGET_CONTAINER" sh -c "command -v mysql >/dev/null" 2>&1 || { echo "本地 Docker 容器 $TARGET_CONTAINER 未找到 mysql 命令。程序中止。" >&2; exit 1; }
            docker exec "$TARGET_CONTAINER" sh -c "command -v mysqldump >/dev/null" 2>&1 || { echo "本地 Docker 容器 $TARGET_CONTAINER 未找到 mysqldump 命令。程序中止。" >&2; exit 1; }
            ;;
        "native")
            command -v mysql >/dev/null 2>&1 || { echo "本地 MySQL 类型为 native,但未找到 mysql 命令。程序中止。" >&2; exit 1; }
            command -v mysqldump >/dev/null 2>&1 || { echo "本地 MySQL 类型为 native,但未找到 mysqldump 命令。程序中止。" >&2; exit 1; }
            ;;
        *)
            echo "本地 MySQL 类型 LOCAL_MYSQL_TYPE 必须为 'native' 或 'docker',当前值:$LOCAL_MYSQL_TYPE。程序中止。" >&2; exit 1;
            ;;
    esac
    case "$REMOTE_MYSQL_TYPE" in
        "docker")
            log "正在检查 REMOTE_MYSQL_TYPE:$REMOTE_MYSQL_TYPE .."
            command -v docker >/dev/null 2>&1 || { echo "远程 MySQL 类型为 docker,但未找到 docker 命令。程序中止。" >&2; exit 1; }
            docker info >/dev/null 2>&1 || { echo "本地 Docker 服务未运行(需要本地 docker 命令检查远程容器)。程序中止。" >&2; exit 1; }
            ssh "$REMOTE_USER@$REMOTE_HOST" "docker exec $SOURCE_CONTAINER sh -c 'command -v mysql >/dev/null'" >/dev/null 2>&1 || { echo "远程 Docker 容器 $SOURCE_CONTAINER 未找到 mysql 命令。程序中止。" >&2; exit 1; }
            ssh "$REMOTE_USER@$REMOTE_HOST" "docker exec $SOURCE_CONTAINER sh -c 'command -v mysqldump >/dev/null'" >/dev/null 2>&1 || { echo "远程 Docker 容器 $SOURCE_CONTAINER 未找到 mysqldump 命令。程序中止。" >&2; exit 1; }
            ;;
        "native")
            ssh "$REMOTE_USER@$REMOTE_HOST" "command -v mysql >/dev/null" >/dev/null 2>&1 || { echo "远程 MySQL 类型为 native,但未找到 mysql 命令。程序中止。" >&2; exit 1; }
            ssh "$REMOTE_USER@$REMOTE_HOST" "command -v mysqldump >/dev/null" >/dev/null 2>&1 || { echo "远程 MySQL 类型为 native,但未找到 mysqldump 命令。程序中止。" >&2; exit 1; }
            ;;
        *)
            echo "远程 MySQL 类型 REMOTE_MYSQL_TYPE 必须为 'native' 或 'docker',当前值:$REMOTE_MYSQL_TYPE。程序中止。" >&2; exit 1;
            ;;
    esac
}

# 创建备份目录
mkdir -p "$BACKUP_DIR"

# 检查容器和连接
check_connection() {
    if [ "$REMOTE_MYSQL_TYPE" = "docker" ]; then
        log "正在检查 $REMOTE_HOST 上的源容器 ($SOURCE_CONTAINER)..."
        ssh "$REMOTE_USER@$REMOTE_HOST" "docker ps | grep $SOURCE_CONTAINER" >/dev/null
        if [ $? -ne 0 ]; then
            log "$REMOTE_HOST 上的源容器 $SOURCE_CONTAINER 未运行"
            exit 1
        else
            log "$REMOTE_HOST 上的源容器 $SOURCE_CONTAINER 已运行"
        fi
    fi

    log "正在测试源数据库连接..."
    if [ "$REMOTE_MYSQL_TYPE" = "docker" ]; then
        ssh "$REMOTE_USER@$REMOTE_HOST" "docker exec $SOURCE_CONTAINER mysql -u $SOURCE_USER --password=$SOURCE_PASS -e 'SELECT 1'" >/dev/null 2>&1
    else
        ssh "$REMOTE_USER@$REMOTE_HOST" "mysql -u $SOURCE_USER --password=$SOURCE_PASS -P $REMOTE_MYSQL_PORT -e 'SELECT 1'" >/dev/null 2>&1
    fi
    if [ $? -ne 0 ]; then
        log "源数据库连接失败"
        exit 1
    else
        log "源数据库连接成功"
    fi

    if [ "$LOCAL_MYSQL_TYPE" = "docker" ]; then
        log "正在检查本地目标容器 ($TARGET_CONTAINER)..."
        docker ps | grep "$TARGET_CONTAINER" >/dev/null
        if [ $? -ne 0 ]; then
            log "本地目标容器 $TARGET_CONTAINER 未运行"
            exit 1
        else
            log "本地目标容器 $TARGET_CONTAINER 已运行"
        fi
    fi

    log "正在测试目标数据库连接..."
    if [ "$LOCAL_MYSQL_TYPE" = "docker" ]; then
        docker exec "$TARGET_CONTAINER" mysql -u "$TARGET_USER" --password="$TARGET_PASS" -e "SELECT 1" >/dev/null 2>&1
    else
        mysql -u "$TARGET_USER" --password="$TARGET_PASS" -P $LOCAL_MYSQL_PORT -e "SELECT 1" >/dev/null 2>&1
    fi
    if [ $? -ne 0 ]; then
        log "目标数据库连接失败"
        exit 1
    else
        log "目标数据库连接成功"
    fi
}

# 备份源数据库(远程)
backup_source() {
    BACKUP_FILE="$BACKUP_DIR/${SOURCE_DB}_${TIMESTAMP}.sql"
    log "开始从 $REMOTE_HOST 备份源数据库..."
    if [ "$REMOTE_MYSQL_TYPE" = "docker" ]; then
        ssh "$REMOTE_USER@$REMOTE_HOST" \
            "docker exec $SOURCE_CONTAINER mysqldump -u $SOURCE_USER --password=$SOURCE_PASS \
             --single-transaction --routines --triggers $SOURCE_DB" > "$BACKUP_FILE" 2>/dev/null
    else
        ssh "$REMOTE_USER@$REMOTE_HOST" \
            "mysqldump -u $SOURCE_USER --password=$SOURCE_PASS -P $REMOTE_MYSQL_PORT \
             --single-transaction --routines --triggers $SOURCE_DB" > "$BACKUP_FILE" 2>/dev/null
    fi
    
    if [ $? -eq 0 ]; then
        if [ -s "$BACKUP_FILE" ] && grep -q "^-- MySQL dump" "$BACKUP_FILE"; then
            log "备份成功完成: $BACKUP_FILE"
        else
            log "备份文件无效或为空!"
            exit 1
        fi
    else
        log "备份失败!"
        exit 1
    fi
}

# 同步到目标数据库(本地)
sync_to_target() {
    log "开始同步到目标数据库..."
    
    if [ "$LOCAL_MYSQL_TYPE" = "docker" ]; then
        cat "$BACKUP_FILE" | docker exec -i "$TARGET_CONTAINER" mysql -u "$TARGET_USER" --password="$TARGET_PASS" "$TARGET_DB" >/dev/null 2>&1
    else
        cat "$BACKUP_FILE" | mysql -u "$TARGET_USER" --password="$TARGET_PASS" -P $LOCAL_MYSQL_PORT "$TARGET_DB" >/dev/null 2>&1
    fi
    
    if [ $? -eq 0 ]; then
        log "同步成功完成"
    else
        log "同步失败!"
        exit 1
    fi
}

# 主执行流程
main() {
    SCRIPT_DIR=$(dirname "$(readlink -f "$0")")

    if [[ "$INTERACTIVE_CONFIRMATION" != "true" && "$OPEN_LOG" != "true" ]]; then
      #开机自启动配置
      LOG_DIR=$SCRIPT_DIR/logs
      mkdir -p $LOG_DIR
      LOGFILE=$LOG_DIR/mysql_remote_to_local_sync.log
      # 重定向所有输出到日志文件
      exec > "$LOGFILE" 2>&1
      log "脚本所在目录: $SCRIPT_DIR"
      log "等待10s本机mysql启动完成"
      sleep 10
    fi
    
    log "远程$SOURCE_DB数据库同步至本地$TARGET_DB数据库"
    log "开始数据库同步流程..."
    check_env
    # 检查命令
    check_required_commands
    # 检查连接
    check_connection
    # 执行备份
    backup_source
    # 执行同步
    sync_to_target    
    # 清理旧的备份文件(保留最近5个)
    cd "$BACKUP_DIR" || exit
    ls -t | grep "${SOURCE_DB}_.*\.sql" | tail -n +6 | xargs -I {} rm {} 2>/dev/null
    log "已清理旧备份文件"
    log "数据库同步成功完成"
}

# 错误处理
set -e
trap 'log "发生错误,程序退出..."; exit 1' ERR

# 执行主函数
main

exit 0

2. mysql_local_to_remote_sync.sh

#!/bin/bash

# 默认配置
DEFAULT_LOCAL_MYSQL_TYPE="docker"                # 本地 MySQL 类型: native 或 docker
DEFAULT_LOCAL_MYSQL_PORT=3306                    # 本地 MySQL 端口(仅 native 模式,默认 3306)
DEFAULT_SOURCE_CONTAINER="<SOURCE_CONTAINER>"     # 本地 Docker 容器名
DEFAULT_SOURCE_USER="<SOURCE_USER>"              # 源数据库用户名
DEFAULT_SOURCE_PASS="<SOURCE_PASS>"              # 源数据库密码
DEFAULT_SOURCE_DB="<SOURCE_DB>"                  # 源数据库名
DEFAULT_REMOTE_USER="<USER>"                     # SSH 用户名
DEFAULT_REMOTE_HOST="<IP>"                       # 远程服务器 IP
DEFAULT_REMOTE_MYSQL_TYPE="docker"               # 远程 MySQL 类型: native 或 docker
DEFAULT_REMOTE_MYSQL_PORT=3306                   # 远程 MySQL 端口(仅 native 模式,默认 3306)
DEFAULT_TARGET_CONTAINER="<TARGET_CONTAINER>"     # 远程 Docker 容器名
DEFAULT_TARGET_USER="<TARGET_USER>"              # 目标数据库用户名
DEFAULT_TARGET_PASS="<TARGET_PASS>"              # 目标数据库密码
DEFAULT_TARGET_DB="<TARGET_DB>"                  # 目标数据库名
DEFAULT_BACKUP_DIR="<BACKUP_DIR>"                # 备份目录
DEFAULT_INTERACTIVE_CONFIRMATION=true            #是否开启交互确认
# 加载现有 .env 文件
if [ -f ".env" ]; then
  export $(cat .env | grep -v '^#' | xargs)
fi

# 本地源数据库信息
LOCAL_MYSQL_TYPE=${LOCAL_MYSQL_TYPE:-$DEFAULT_LOCAL_MYSQL_TYPE}
LOCAL_MYSQL_PORT=${LOCAL_MYSQL_PORT:-$DEFAULT_LOCAL_MYSQL_PORT}
SOURCE_CONTAINER=${SOURCE_CONTAINER:-$DEFAULT_SOURCE_CONTAINER}
SOURCE_USER=${SOURCE_USER:-$DEFAULT_SOURCE_USER}
SOURCE_PASS=${SOURCE_PASS:-$DEFAULT_SOURCE_PASS}
SOURCE_DB=${SOURCE_DB:-$DEFAULT_SOURCE_DB}

# 远程目标数据库信息
REMOTE_USER=${REMOTE_USER:-$DEFAULT_REMOTE_USER}
REMOTE_HOST=${REMOTE_HOST:-$DEFAULT_REMOTE_HOST}
REMOTE_MYSQL_TYPE=${REMOTE_MYSQL_TYPE:-$DEFAULT_REMOTE_MYSQL_TYPE}
REMOTE_MYSQL_PORT=${REMOTE_MYSQL_PORT:-$DEFAULT_REMOTE_MYSQL_PORT}
TARGET_CONTAINER=${TARGET_CONTAINER:-$DEFAULT_TARGET_CONTAINER}
TARGET_USER=${TARGET_USER:-$DEFAULT_TARGET_USER}
TARGET_PASS=${TARGET_PASS:-$DEFAULT_TARGET_PASS}
TARGET_DB=${TARGET_DB:-$DEFAULT_TARGET_DB}

# 备份设置
BACKUP_DIR=${BACKUP_DIR:-$DEFAULT_BACKUP_DIR}
#是否开启交互确认
INTERACTIVE_CONFIRMATION=${INTERACTIVE_CONFIRMATION:-$DEFAULT_INTERACTIVE_CONFIRMATION} 
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

OPEN_LOG=true

SH_INTERACTIVE_CONFIRMATION=$1
SH_OPEN_LOG=$2

# 日志记录函数
log() {
#前景色(字体颜色):黑色(BK):30|红色(RD):31|绿色(GN):32|
#黄色(YW):33|蓝色(BE):34|紫色(PE):35|青色(CN):36|白色(WE):37 
    color=36
    if [ "$2" != "" ];then
        read_color="$2"
        if [ "$read_color" == "BK" ]; then
            color=30
        elif [ "$read_color" == "RD" ]; then
            color=31
        elif [ "$read_color" == "GN" ]; then
            color=32
        elif [ "$read_color" == "YW" ]; then
            color=33
        elif [ "$read_color" == "BE" ]; then
            color=34
        elif [ "$read_color" == "PE" ]; then
            color=35
        elif [ "$read_color" == "WE" ]; then
            color=36
        else
            color=37
        fi
    fi
    echo -e "\033[${color}m[$(date '+%Y-%m-%d %H:%M:%S')] $1\033[0m"
    #echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

log "SH_INTERACTIVE_CONFIRMATION:$SH_INTERACTIVE_CONFIRMATION"
log "SH_OPEN_LOG:$SH_OPEN_LOG"
if [ "$SH_INTERACTIVE_CONFIRMATION" != "" ]; then
INTERACTIVE_CONFIRMATION=$SH_INTERACTIVE_CONFIRMATION
fi
if [ "$SH_OPEN_LOG" != "" ]; then
OPEN_LOG=$SH_OPEN_LOG
fi
#======================================================================================#
check_env() {
log "
>==============<
|远程服务器信息|
>==============<
SSH用户名:
REMOTE_USER=$REMOTE_USER                  
远程服务器IP:                                
REMOTE_HOST=$REMOTE_HOST                  
远程 MySQL类型(native或 docker):
REMOTE_MYSQL_TYPE=$REMOTE_MYSQL_TYPE      
远程 MySQL端口(仅 native模式,默认3306):
REMOTE_MYSQL_PORT=$REMOTE_MYSQL_PORT 
远程 Docker容器名:
SOURCE_CONTAINER=$SOURCE_CONTAINER       
源数据库用户名:     
SOURCE_USER=$SOURCE_USER                 
源数据库密码:    
SOURCE_PASS=$SOURCE_PASS                 
源数据库名:
SOURCE_DB=$SOURCE_DB                      
>===================<
|本地目标数据库信息|
>===================<
本地 MySQL类型(native或 docker):
LOCAL_MYSQL_TYPE=$LOCAL_MYSQL_TYPE       
本地 MySQL端口(仅 native模式,默认3306):
LOCAL_MYSQL_PORT=$LOCAL_MYSQL_PORT     
本地 Docker容器名:
TARGET_CONTAINER=$TARGET_CONTAINER        
目标数据库用户名:
TARGET_USER=$TARGET_USER                  
目标数据库密码:
TARGET_PASS=$TARGET_PASS                 
目标数据库名:
TARGET_DB=$TARGET_DB                     
备份设置:
BACKUP_DIR=$BACKUP_DIR"      
      if [ "$INTERACTIVE_CONFIRMATION" == "true" ]; then
          log "交互确认已开启"
          read -r -p "[$(date '+%Y-%m-%d %H:%M:%S')] 请核实配置是否一致? [y/N] " key
          if [[ "${key,,}" =~ ^(yes|y)$ ]]
          then
              echo ""
          else
              exit 0
          fi
      else
          log "交互确认已关闭"
      fi
}

# 检查必要的命令
check_required_commands() {
    log "正在检查 LOCAL_MYSQL_TYPE REMOTE_MYSQL_TYPE .."
    command -v ssh >/dev/null 2>&1 || { echo "需要安装 ssh,但未找到。程序中止。" >&2; exit 1; }
    case "$LOCAL_MYSQL_TYPE" in
        "docker")
            log "正在检查 LOCAL_MYSQL_TYPE:$LOCAL_MYSQL_TYPE .."
            command -v docker >/dev/null 2>&1 || { echo "本地 MySQL 类型为 docker,但未找到 docker 命令。程序中止。" >&2; exit 1; }
            docker info >/dev/null 2>&1 || { echo "本地 Docker 服务未运行。程序中止。" >&2; exit 1; }
            docker exec "$SOURCE_CONTAINER" sh -c "command -v mysql >/dev/null" 2>&1 || { echo "本地 Docker 容器 $SOURCE_CONTAINER 未找到 mysql 命令。程序中止。" >&2; exit 1; }
            docker exec "$SOURCE_CONTAINER" sh -c "command -v mysqldump >/dev/null" 2>&1 || { echo "本地 Docker 容器 $SOURCE_CONTAINER 未找到 mysqldump 命令。程序中止。" >&2; exit 1; }
            ;;
        "native")
            command -v mysql >/dev/null 2>&1 || { echo "本地 MySQL 类型为 native,但未找到 mysql 命令。程序中止。" >&2; exit 1; }
            command -v mysqldump >/dev/null 2>&1 || { echo "本地 MySQL 类型为 native,但未找到 mysqldump 命令。程序中止。" >&2; exit 1; }
            ;;
        *)
            echo "本地 MySQL 类型 LOCAL_MYSQL_TYPE 必须为 'native' 或 'docker',当前值:$LOCAL_MYSQL_TYPE。程序中止。" >&2; exit 1;
            ;;
    esac
    case "$REMOTE_MYSQL_TYPE" in
        "docker")
            log "正在检查 REMOTE_MYSQL_TYPE:$REMOTE_MYSQL_TYPE .."
            command -v docker >/dev/null 2>&1 || { echo "远程 MySQL 类型为 docker,但未找到 docker 命令。程序中止。" >&2; exit 1; }
            docker info >/dev/null 2>&1 || { echo "本地 Docker 服务未运行(需要本地 docker 命令检查远程容器)。程序中止。" >&2; exit 1; }
            ssh "$REMOTE_USER@$REMOTE_HOST" "docker exec $TARGET_CONTAINER sh -c 'command -v mysql >/dev/null'" >/dev/null 2>&1 || { echo "远程 Docker 容器 $TARGET_CONTAINER 未找到 mysql 命令。程序中止。" >&2; exit 1; }
            ssh "$REMOTE_USER@$REMOTE_HOST" "docker exec $TARGET_CONTAINER sh -c 'command -v mysqldump >/dev/null'" >/dev/null 2>&1 || { echo "远程 Docker 容器 $TARGET_CONTAINER 未找到 mysqldump 命令。程序中止。" >&2; exit 1; }
            ;;
        "native")
            ssh "$REMOTE_USER@$REMOTE_HOST" "command -v mysql >/dev/null" >/dev/null 2>&1 || { echo "远程 MySQL 类型为 native,但未找到 mysql 命令。程序中止。" >&2; exit 1; }
            ssh "$REMOTE_USER@$REMOTE_HOST" "command -v mysqldump >/dev/null" >/dev/null 2>&1 || { echo "远程 MySQL 类型为 native,但未找到 mysqldump 命令。程序中止。" >&2; exit 1; }
            ;;
        *)
            echo "远程 MySQL 类型 REMOTE_MYSQL_TYPE 必须为 'native' 或 'docker',当前值:$REMOTE_MYSQL_TYPE。程序中止。" >&2; exit 1;
            ;;
    esac
}

# 创建备份目录
mkdir -p "$BACKUP_DIR"

# 检查容器和连接
check_connection() {
    if [ "$LOCAL_MYSQL_TYPE" = "docker" ]; then
        log "正在检查本地源容器 ($SOURCE_CONTAINER)..."
        docker ps | grep "$SOURCE_CONTAINER" >/dev/null
        if [ $? -ne 0 ]; then
            log "本地源容器 $SOURCE_CONTAINER 未运行"
            exit 1
        else
            log "本地源容器 $SOURCE_CONTAINER 已运行"
        fi
    fi

    log "正在测试源数据库连接..."
    if [ "$LOCAL_MYSQL_TYPE" = "docker" ]; then
        docker exec "$SOURCE_CONTAINER" mysql -u "$SOURCE_USER" --password="$SOURCE_PASS" -e "SELECT 1" >/dev/null 2>&1
    else
        mysql -u "$SOURCE_USER" --password="$SOURCE_PASS" -P $LOCAL_MYSQL_PORT -e "SELECT 1" >/dev/null 2>&1
    fi
    if [ $? -ne 0 ]; then
        log "源数据库连接失败"
        exit 1
    else
        log "源数据库连接成功"
    fi

    if [ "$REMOTE_MYSQL_TYPE" = "docker" ]; then
        log "正在检查 $REMOTE_HOST 上的目标容器 ($TARGET_CONTAINER)..."
        ssh "$REMOTE_USER@$REMOTE_HOST" "docker ps | grep $TARGET_CONTAINER" >/dev/null
        if [ $? -ne 0 ]; then
            log "$REMOTE_HOST 上的目标容器 $TARGET_CONTAINER 未运行"
            exit 1
        else
            log "$REMOTE_HOST 上的目标容器 $TARGET_CONTAINER 已运行"
        fi
    fi

    log "正在测试目标数据库连接..."
    if [ "$REMOTE_MYSQL_TYPE" = "docker" ]; then
        ssh "$REMOTE_USER@$REMOTE_HOST" "docker exec $TARGET_CONTAINER mysql -u $TARGET_USER --password=$TARGET_PASS -e 'SELECT 1'" >/dev/null 2>&1
    else
        ssh "$REMOTE_USER@$REMOTE_HOST" "mysql -u $TARGET_USER --password=$TARGET_PASS -P $REMOTE_MYSQL_PORT -e 'SELECT 1'" >/dev/null 2>&1
    fi
    if [ $? -ne 0 ]; then
        log "目标数据库连接失败"
        exit 1
    else
        log "目标数据库连接成功"
    fi
}

# 备份源数据库(本地)
backup_source() {
    BACKUP_FILE="$BACKUP_DIR/${SOURCE_DB}_${TIMESTAMP}.sql"
    log "开始从本地备份源数据库..."
    
    if [ "$LOCAL_MYSQL_TYPE" = "docker" ]; then
        docker exec "$SOURCE_CONTAINER" mysqldump -u "$SOURCE_USER" --password="$SOURCE_PASS" \
            --single-transaction --routines --triggers "$SOURCE_DB" > "$BACKUP_FILE" 2>/dev/null
    else
        mysqldump -u "$SOURCE_USER" --password="$SOURCE_PASS" -P $LOCAL_MYSQL_PORT \
            --single-transaction --routines --triggers "$SOURCE_DB" > "$BACKUP_FILE" 2>/dev/null
    fi
    
    if [ $? -eq 0 ]; then
        if [ -s "$BACKUP_FILE" ] && grep -q "^-- MySQL dump" "$BACKUP_FILE"; then
            log "备份成功完成: $BACKUP_FILE"
        else
            log "备份文件无效或为空!"
            exit 1
        fi
    else
        log "备份失败!"
        exit 1
    fi
}

# 同步到目标数据库(远程)
sync_to_target() {
    log "开始同步到目标数据库..."
    
    if [ "$REMOTE_MYSQL_TYPE" = "docker" ]; then
        cat  "$BACKUP_FILE" | ssh "$REMOTE_USER@$REMOTE_HOST" \
            "docker exec -i $TARGET_CONTAINER mysql -u $TARGET_USER --password=$TARGET_PASS $TARGET_DB" >/dev/null 2>&1
    else
        cat  "$BACKUP_FILE" | ssh "$REMOTE_USER@$REMOTE_HOST" \
            "mysql -u $TARGET_USER --password=$TARGET_PASS -P $REMOTE_MYSQL_PORT $TARGET_DB" >/dev/null 2>&1
    fi
    
    if  [ $? -eq 0 ]; then
            log "同步成功完成"
        else
            log "同步失败!"
            exit 1
    fi
}

# 主执行流程
main() {
    SCRIPT_DIR=$(dirname "$(readlink -f "$0")")

    if [[ "$INTERACTIVE_CONFIRMATION" != "true" && "$OPEN_LOG" != "true" ]]; then
      LOG_DIR=$SCRIPT_DIR/logs
      mkdir -p $LOG_DIR
      LOGFILE=$LOG_DIR/mysql_local_to_remote_sync.log
      # 重定向所有输出到日志文件
      exec > "$LOGFILE" 2>&1
    fi
    log "本地$SOURCE_DB数据库同步至远程$TARGET_DB数据库"
    log " 开始数据库同步流程..."
    check_env
    # 检查命令
    check_required_commands
    # 检查连接
    check_connection
    # 执行备份
    backup_source
    # 执行同步
    sync_to_target
    # 清理旧的备份文件(保留最近5个)
    cd "$BACKUP_DIR" || exit
    ls -t | grep "${SOURCE_DB}_.*\.sql" | tail -n +6 | xargs -I {} rm {} 2>/dev/null
    log "已清理旧备份"
    log "数据库同步成功完成"
}
# 错误处理
set -e -o pipefail
trap 'log "发生错误,程序退出..."' ERR

# 执行主函数
main

exit 0

注意事项

  1. 安全性

    • 限制 .env 文件权限:
      chmod 600 .env
      
    • 使用 ~/.my.cnf 存储 MySQL 凭据:
      echo "[client]\nuser=[db_user]\npassword=[db_pass]" > ~/.my.cnf
      ssh user@192.168.1.100 "echo '[client]\nuser=[db_user]\npassword=[db_pass]' > ~/.my.cnf"
      
      移除脚本中的 --password 参数。
  2. 性能

    • 大数据库建议压缩:
      # 备份
      ... mysqldump ... | gzip >  "$BACKUP_DIR${