MySQL 数据库同步指南
本文提供两个 Bash 脚本,用于在本地和远程服务器之间同步 MySQL 数据库,支持本机运行的 MySQL(native
)和 Docker 容器中的 MySQL(docker
):
mysql_remote_to_local_sync.sh
:将远程服务器的 MySQL 数据库同步到本地。mysql_local_to_remote_sync.sh
:将本地 MySQL 数据库同步到远程服务器。
脚本特点:
- 支持从
.env
文件加载配置,定义默认变量。 - 在运行前显示配置信息,要求用户确认。
- 支持
native
模式的自定义 MySQL 端口(默认 3306)。 - 日志以中文输出到控制台,敏感信息(如密码)使用占位符。
- 检查必要命令(如
mysql
、mysqldump
、docker
、ssh
)和数据库连接。 - 自动备份数据库,清理旧备份文件(保留最近 5 个)。
环境假设
- 远程服务器:
- IP:
<IP>
- SSH 用户:
<USER>
- MySQL 类型:
<REMOTE_MYSQL_TYPE>
(native
或docker
) - 端口:
<REMOTE_MYSQL_PORT>
(native
模式,默认 3306) - 容器名(
docker
模式):<SOURCE_CONTAINER>
或<TARGET_CONTAINER>
- 数据库:
<SOURCE_DB>
或<TARGET_DB>
- 用户:
<SOURCE_USER>
或<TARGET_USER>
- 密码:
<PASSWORD>
- IP:
- 本地主机:
- MySQL 类型:
<LOCAL_MYSQL_TYPE>
(docker
或native
) - 端口:
<LOCAL_MYSQL_PORT>
(native
模式,默认 3306) - 容器名(docker
模式):<SOURCE>
或<DATABASE>
- 数据库:<SOURCE>
或<DATABASE>
- 用户:<USER>
或<TARGET_USER>
- 密码:
<PASSWORD>
- MySQL 类型:
- 备份目录:
<BACKUP_DIR>
- 要求:
- 本地和远程主机安装
docker
和ssh
(若使用docker
),或mysql
和mysqldump
(若使用native
)。 - SSH 配置免密登录。
- 可选:
.env
文件覆盖默认配置。
- 本地和远程主机安装
脚本功能
1. 远程到本地同步 (mysql_remote_to_local_sync.sh
)
-
功能:
- 从远程 MySQL(
native
或docker
)备份数据库<SOURCE_DB>
。 - 将备份文件传输到本地,导入本地 MySQL(
native
或docker
)。 - 清理旧备份文件(保留最近 5 个)。
- 支持
.env
文件配置、默认变量、用户确认。 - 日志以中文记录,包括
LOCAL_MYSQL_TYPE
和REMOTE_MYSQL_TYPE
检查。
- 从远程 MySQL(
-
流程:
- 加载
.env
文件,应用默认变量。 - 显示配置信息,要求用户输入
y/yes
确认。 - 检查命令(
ssh
、docker
、mysql
、mysqldump
)。 - 验证远程和本地数据库连接。
- 备份远程数据库到本地
<BACKUP_DIR>
。 - 导入备份到本地目标数据库。
- 清理旧备份文件。
- 加载
2. 本地到远程同步 (mysql_local_to_remote_sync.sh
)
-
功能:
- 从本地 MySQL(
native
或docker
)备份数据库<SOURCE_DB>
。 - 将备份文件传输到远程服务器,导入远程 MySQL(
native
或docker
)。 - 清理本地旧备份文件(保留最近 5 个)。
- 支持
.env
文件配置、默认变量、用户确认。 - 日志以中文记录,包括
LOCAL_MYSQL_TYPE
和REMOTE_MYSQL_TYPE
检查。
- 从本地 MySQL(
-
流程:
- 加载
.env
文件,应用默认变量。 - 显示配置信息,要求用户输入
y/yes
确认。 - 检查命令(
ssh
、docker
、mysql
、mysqldump
)。 - 验证本地和远程数据库连接。
- 备份本地数据库到
<BACKUP_DIR>
。 - 传输备份文件并导入远程目标数据库。
- 清理旧备份文件。
- 加载
配置步骤
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
- Docker:
-
启动本地 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
非默认端口(如 3307):GRANT ALL PRIVILEGES ON localdb.* TO 'db_user'@'localhost' IDENTIFIED BY 'db_pass'; FLUSH PRIVILEGES;
修改sudo vim /etc/mysql/my.cnf
[mysqld]
:
重启:port = 3307
sudo systemctl restart mysql
- Docker:
-
创建备份目录:
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"
- Docker:
-
启动远程 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"
非默认端口(如 3307),修改GRANT ALL PRIVILEGES ON mydb.* TO 'db_user'@'%' IDENTIFIED BY 'db_pass'; FLUSH PRIVILEGES;
/etc/mysql/my.cnf
并重启。
- Docker:
-
配置 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:
-
远程数据库:
- 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'"
- Docker:
使用方法
-
保存脚本:
- 远程到本地:
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
- 远程到本地:
-
配置:
- 优先级:
.env
文件 > 环境变量 > 默认变量。 - 创建
.env
文件,或修改脚本中的DEFAULT_*
变量。 - 确保
<BACKUP_DIR>
存在且可写。
- 优先级:
-
运行脚本:
- 远程到本地:
./mysql_remote_to_local_sync.sh
- 本地到远程:
./mysql_local_to_remote_sync.sh
- 脚本显示配置并提示确认:
>==============< |远程服务器信息| SSH用户名: user 远程服务器IP: 192.168.1.100 ... 请核实配置是否一致? [y/N]
- 输入
y
或yes
继续,否则退出。
- 远程到本地:
-
预期输出:
[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
注意事项
-
安全性:
- 限制
.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
参数。
- 限制
-
性能:
- 大数据库建议压缩:
# 备份 ... mysqldump ... | gzip > "$BACKUP_DIR${
- 大数据库建议压缩: