345 lines
12 KiB
Bash
Executable File
345 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
# ============================================================
|
|
# 表结构对齐脚本 - 准生产 → VPS
|
|
# ============================================================
|
|
# 用法:./sync_tables.sh <source_database> [target_database]
|
|
# 示例:./sync_tables.sh dmp_serp
|
|
# ./sync_tables.sh dmp_smdm dmp_smdm
|
|
# ============================================================
|
|
|
|
# 去掉 set -e,允许错误继续执行
|
|
|
|
# 加载配置
|
|
CONFIG_FILE="/home/yangxuan/.openclaw/workspace-sql/.db-sync-config.yaml"
|
|
SCRIPT_DIR="/home/yangxuan/codes/xuan-sql/scripts"
|
|
MEMORY_DIR="/home/yangxuan/.openclaw/workspace-sql/memory"
|
|
|
|
# 解析参数
|
|
SOURCE_DB="${1:-}"
|
|
TARGET_DB="${2:-$SOURCE_DB}"
|
|
|
|
if [ -z "$SOURCE_DB" ]; then
|
|
echo "用法:$0 <source_database> [target_database]"
|
|
echo "示例:$0 dmp_serp"
|
|
exit 1
|
|
fi
|
|
|
|
# 从配置文件读取连接信息(简单 YAML 解析)
|
|
SOURCE_HOST=$(grep -A5 "name: 准生产" "$CONFIG_FILE" | grep "host:" | head -1 | awk '{print $2}')
|
|
SOURCE_PORT=$(grep -A5 "name: 准生产" "$CONFIG_FILE" | grep "port:" | head -1 | awk '{print $2}')
|
|
SOURCE_USER=$(grep -A5 "name: 准生产" "$CONFIG_FILE" | grep "user:" | head -1 | awk '{print $2}')
|
|
SOURCE_PWD_ENV=$(grep -A5 "name: 准生产" "$CONFIG_FILE" | grep "password_env:" | head -1 | awk '{print $2}')
|
|
|
|
TARGET_HOST=$(grep -A5 "name: VPS" "$CONFIG_FILE" | grep "host:" | head -1 | awk '{print $2}')
|
|
TARGET_PORT=$(grep -A5 "name: VPS" "$CONFIG_FILE" | grep "port:" | head -1 | awk '{print $2}')
|
|
TARGET_USER=$(grep -A5 "name: VPS" "$CONFIG_FILE" | grep "user:" | head -1 | awk '{print $2}')
|
|
TARGET_PWD_ENV=$(grep -A5 "name: VPS" "$CONFIG_FILE" | grep "password_env:" | head -1 | awk '{print $2}')
|
|
|
|
# 获取密码
|
|
SOURCE_PWD="${!SOURCE_PWD_ENV}"
|
|
TARGET_PWD="${!TARGET_PWD_ENV}"
|
|
|
|
if [ -z "$SOURCE_PWD" ] || [ -z "$TARGET_PWD" ]; then
|
|
echo "错误:密码环境变量未设置"
|
|
echo "请确保已设置:$SOURCE_PWD_ENV 和 $TARGET_PWD_ENV"
|
|
exit 1
|
|
fi
|
|
|
|
# 生成输出目录
|
|
DATE_STR=$(date +%Y%m%d)
|
|
OUTPUT_DIR="$SCRIPT_DIR/$DATE_STR"
|
|
mkdir -p "$OUTPUT_DIR"
|
|
|
|
echo "============================================================"
|
|
echo "表结构对齐 - 准生产 → VPS"
|
|
echo "============================================================"
|
|
echo "源数据库:$SOURCE_HOST:$SOURCE_PORT / $SOURCE_DB"
|
|
echo "目标数据库:$TARGET_HOST:$TARGET_PORT / $TARGET_DB"
|
|
echo "输出目录:$OUTPUT_DIR"
|
|
echo "============================================================"
|
|
|
|
# 验证连接
|
|
echo ""
|
|
echo "[1/5] 验证数据库连接..."
|
|
|
|
# 验证源库
|
|
if ! mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"$SOURCE_PWD" -e "SELECT 1;" > /dev/null 2>&1; then
|
|
echo "错误:无法连接到源数据库(准生产)"
|
|
exit 1
|
|
fi
|
|
echo "✓ 源数据库连接成功"
|
|
|
|
# 验证目标库
|
|
if ! mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -e "SELECT 1;" > /dev/null 2>&1; then
|
|
echo "错误:无法连接到目标数据库(VPS)"
|
|
exit 1
|
|
fi
|
|
echo "✓ 目标数据库连接成功"
|
|
|
|
# 获取表列表
|
|
echo ""
|
|
echo "[2/5] 获取源数据库表列表..."
|
|
|
|
TABLES=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"$SOURCE_PWD" -D "$SOURCE_DB" -N -e "SHOW TABLES;" 2>/dev/null)
|
|
TABLE_COUNT=$(echo "$TABLES" | wc -l)
|
|
|
|
echo "找到 $TABLE_COUNT 张表"
|
|
|
|
if [ "$TABLE_COUNT" -gt 50 ]; then
|
|
read -p "表数量超过 50 张(当前:$TABLE_COUNT),是否继续?[y/N] " -n 1 -r
|
|
echo
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
echo "已取消"
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
# 初始化统计
|
|
SUCCESS_COUNT=0
|
|
FAIL_COUNT=0
|
|
declare -a TABLE_STATUS=()
|
|
|
|
# 处理每个表
|
|
echo ""
|
|
echo "[3/5] 对比并生成脚本..."
|
|
|
|
for TABLE in $TABLES; do
|
|
echo ""
|
|
echo "处理表:$TABLE"
|
|
|
|
SCRIPT_FILE="$OUTPUT_DIR/${TARGET_DB}__${TABLE}__struct.sql"
|
|
|
|
# 获取源表结构
|
|
SOURCE_DDL=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"$SOURCE_PWD" -D "$SOURCE_DB" -N -e "SHOW CREATE TABLE $TABLE\G" 2>/dev/null)
|
|
|
|
# 检查目标表是否存在
|
|
TARGET_EXISTS=$(mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -D "$TARGET_DB" -N -e "SHOW TABLES LIKE '$TABLE';" 2>/dev/null | wc -l)
|
|
|
|
if [ "$TARGET_EXISTS" -eq 0 ]; then
|
|
# 目标表不存在,创建表
|
|
echo " → 目标表不存在,执行 CREATE TABLE"
|
|
|
|
# 提取 CREATE TABLE 语句
|
|
CREATE_STMT=$(echo "$SOURCE_DDL" | grep -A1000 "Create Table:" | tail -n +2 | sed 's/^ //')
|
|
|
|
# 生成脚本
|
|
cat > "$SCRIPT_FILE" << EOF
|
|
-- ============================================================
|
|
-- 表结构同步脚本:$TABLE
|
|
-- 源:准生产 ($SOURCE_HOST:$SOURCE_PORT)
|
|
-- 目标:VPS ($TARGET_HOST:$TARGET_PORT) $TARGET_DB
|
|
-- 生成时间:$(date '+%Y-%m-%d %H:%M:%S')
|
|
-- 操作:CREATE TABLE(目标表不存在)
|
|
-- ============================================================
|
|
|
|
USE $TARGET_DB;
|
|
|
|
$CREATE_STMT
|
|
|
|
-- ============================================================
|
|
-- 验证
|
|
-- ============================================================
|
|
-- SHOW CREATE TABLE $TABLE\\G
|
|
EOF
|
|
|
|
# 执行脚本
|
|
if mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -D "$TARGET_DB" < "$SCRIPT_FILE" 2>/dev/null; then
|
|
echo " ✓ CREATE TABLE 成功"
|
|
((SUCCESS_COUNT++))
|
|
TABLE_STATUS+=("| $TABLE | CREATE | ✅ | 新建表 |")
|
|
else
|
|
echo " ✗ CREATE TABLE 失败"
|
|
((FAIL_COUNT++))
|
|
TABLE_STATUS+=("| $TABLE | CREATE | ❌ | 创建失败 |")
|
|
fi
|
|
else
|
|
# 目标表已存在,对比结构
|
|
echo " → 目标表已存在,对比结构差异..."
|
|
|
|
TARGET_DDL=$(mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -D "$TARGET_DB" -N -e "SHOW CREATE TABLE $TABLE\G" 2>/dev/null)
|
|
|
|
# 获取字段列表对比
|
|
SOURCE_FIELDS=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"$SOURCE_PWD" -D "$SOURCE_DB" -N -e "
|
|
SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, IFNULL(COLUMN_DEFAULT,'NULL'), IFNULL(COLUMN_COMMENT,'')
|
|
FROM information_schema.COLUMNS
|
|
WHERE TABLE_SCHEMA = '$SOURCE_DB' AND TABLE_NAME = '$TABLE'
|
|
ORDER BY ORDINAL_POSITION;
|
|
" 2>/dev/null)
|
|
|
|
TARGET_FIELDS=$(mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -D "$TARGET_DB" -N -e "
|
|
SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, IFNULL(COLUMN_DEFAULT,'NULL'), IFNULL(COLUMN_COMMENT,'')
|
|
FROM information_schema.COLUMNS
|
|
WHERE TABLE_SCHEMA = '$TARGET_DB' AND TABLE_NAME = '$TABLE'
|
|
ORDER BY ORDINAL_POSITION;
|
|
" 2>/dev/null)
|
|
|
|
# 生成 ALTER 语句(简化版:只处理字段增删改)
|
|
ALTER_STMT=""
|
|
CHANGES=""
|
|
|
|
# 检查源有目标无的字段(添加)
|
|
while IFS='|' read -r COL_NAME COL_TYPE IS_NULL COL_DEF COL_COMMENT; do
|
|
if ! echo "$TARGET_FIELDS" | grep -q "^$COL_NAME|"; then
|
|
# 字段不存在,添加
|
|
NULLABLE="$([ "$IS_NULL" = "YES" ] && echo "NULL" || echo "NOT NULL")"
|
|
DEFAULT="$([ "$COL_DEF" != "NULL" ] && echo "DEFAULT $COL_DEF" || echo "")"
|
|
COMMENT="$([ -n "$COL_COMMENT" ] && echo "COMMENT '$COL_COMMENT'" || echo "")"
|
|
|
|
ALTER_STMT="$ALTER_STMT
|
|
ALTER TABLE $TABLE ADD COLUMN $COL_NAME $COL_TYPE $NULLABLE $DEFAULT $COMMENT;"
|
|
CHANGES="$CHANGES\n - 添加字段:$COL_NAME"
|
|
fi
|
|
done <<< "$SOURCE_FIELDS"
|
|
|
|
# 检查目标有源无的字段(删除)
|
|
while IFS='|' read -r COL_NAME COL_TYPE IS_NULL COL_DEF COL_COMMENT; do
|
|
if ! echo "$SOURCE_FIELDS" | grep -q "^$COL_NAME|"; then
|
|
# 源不存在,删除
|
|
ALTER_STMT="$ALTER_STMT
|
|
ALTER TABLE $TABLE DROP COLUMN $COL_NAME;"
|
|
CHANGES="$CHANGES\n - 删除字段:$COL_NAME"
|
|
fi
|
|
done <<< "$TARGET_FIELDS"
|
|
|
|
if [ -n "$ALTER_STMT" ]; then
|
|
echo " → 发现结构差异,生成 ALTER 语句"
|
|
|
|
# 生成脚本
|
|
cat > "$SCRIPT_FILE" << EOF
|
|
-- ============================================================
|
|
-- 表结构同步脚本:$TABLE
|
|
-- 源:准生产 ($SOURCE_HOST:$SOURCE_PORT)
|
|
-- 目标:VPS ($TARGET_HOST:$TARGET_PORT) $TARGET_DB
|
|
-- 生成时间:$(date '+%Y-%m-%d %H:%M:%S')
|
|
-- 操作:ALTER TABLE(结构差异)
|
|
-- ============================================================
|
|
|
|
USE $TARGET_DB;
|
|
-- 结构差异:$(echo -e "$CHANGES")
|
|
|
|
$ALTER_STMT
|
|
|
|
-- ============================================================
|
|
-- 验证
|
|
-- ============================================================
|
|
-- SHOW CREATE TABLE $TABLE\\G
|
|
EOF
|
|
|
|
# 执行脚本(失败继续)
|
|
if mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -D "$TARGET_DB" < "$SCRIPT_FILE" 2>/dev/null; then
|
|
echo " ✓ ALTER TABLE 成功"
|
|
((SUCCESS_COUNT++))
|
|
TABLE_STATUS+=("| $TABLE | ALTER | ✅ | $(echo -e "$CHANGES" | wc -l) 处变更 |")
|
|
else
|
|
echo " ✗ ALTER TABLE 失败(继续执行下一个表)"
|
|
((FAIL_COUNT++))
|
|
TABLE_STATUS+=("| $TABLE | ALTER | ❌ | 执行失败 |")
|
|
fi
|
|
else
|
|
echo " ✓ 结构一致,无需修改"
|
|
((SUCCESS_COUNT++))
|
|
TABLE_STATUS+=("| $TABLE | SKIP | ✅ | 结构一致 |")
|
|
rm -f "$SCRIPT_FILE" # 删除空脚本
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# 生成 README
|
|
echo ""
|
|
echo "[4/5] 生成 README.md..."
|
|
|
|
cat > "$OUTPUT_DIR/README.md" << EOF
|
|
# 表结构同步脚本 - $DATE_STR
|
|
|
|
**源:** 准生产 \`$SOURCE_HOST:$SOURCE_PORT\` \`$SOURCE_DB\`
|
|
**目标:** VPS \`$TARGET_HOST:$TARGET_PORT\` \`$TARGET_DB\`
|
|
**生成时间:** $(date '+%Y-%m-%d %H:%M:%S')
|
|
**执行状态:** $([ $FAIL_COUNT -eq 0 ] && echo "✅ 已完成" || echo "⚠️ 部分失败")
|
|
|
|
## 脚本清单
|
|
|
|
| 序号 | 脚本文件 | 操作类型 | 状态 | 说明 |
|
|
|------|---------|---------|------|------|
|
|
EOF
|
|
|
|
i=1
|
|
for STATUS in "${TABLE_STATUS[@]}"; do
|
|
TABLE_NAME=$(echo "$STATUS" | cut -d'|' -f2 | tr -d ' ')
|
|
SCRIPT_NAME="${TARGET_DB}__${TABLE_NAME}__struct.sql"
|
|
echo "| $i | \`$SCRIPT_NAME\` $STATUS" >> "$OUTPUT_DIR/README.md"
|
|
((i++))
|
|
done
|
|
|
|
cat >> "$OUTPUT_DIR/README.md" << EOF
|
|
|
|
## 执行结果
|
|
|
|
- 总表数:$TABLE_COUNT
|
|
- 成功:$SUCCESS_COUNT
|
|
- 失败:$FAIL_COUNT
|
|
|
|
## 验证命令
|
|
|
|
\`\`\`sql
|
|
-- 检查表列表
|
|
SHOW TABLES LIKE '$TARGET_DB%';
|
|
|
|
-- 检查表结构
|
|
SHOW CREATE TABLE <table_name>\\G
|
|
\`\`\`
|
|
EOF
|
|
|
|
# 记录到记忆文件
|
|
echo ""
|
|
echo "[5/5] 记录到记忆文件..."
|
|
|
|
MEMORY_FILE="$MEMORY_DIR/$(date +%Y-%m-%d).md"
|
|
|
|
if [ ! -f "$MEMORY_FILE" ]; then
|
|
echo "# $(date +%Y-%m-%d) 工作记录" > "$MEMORY_FILE"
|
|
echo "" >> "$MEMORY_FILE"
|
|
fi
|
|
|
|
cat >> "$MEMORY_FILE" << EOF
|
|
|
|
## 表结构同步 - $(date '+%H:%M')
|
|
|
|
**源:** 准生产 \`$SOURCE_DB\`
|
|
**目标:** VPS \`$TARGET_DB\`
|
|
**处理表数:** $TABLE_COUNT
|
|
**成功:** $SUCCESS_COUNT
|
|
**失败:** $FAIL_COUNT
|
|
|
|
### 处理的表
|
|
|
|
| 表名 | 操作 | 状态 | 说明 |
|
|
|------|------|------|------|
|
|
EOF
|
|
|
|
for STATUS in "${TABLE_STATUS[@]}"; do
|
|
echo "$STATUS" >> "$MEMORY_FILE"
|
|
done
|
|
|
|
cat >> "$MEMORY_FILE" << EOF
|
|
|
|
### 脚本位置
|
|
|
|
\`\`\`
|
|
$OUTPUT_DIR/
|
|
├── README.md
|
|
└── ${TARGET_DB}__*_struct.sql
|
|
\`\`\`
|
|
|
|
EOF
|
|
|
|
echo ""
|
|
echo "============================================================"
|
|
echo "执行完成!"
|
|
echo "============================================================"
|
|
echo "总表数:$TABLE_COUNT"
|
|
echo "成功:$SUCCESS_COUNT"
|
|
echo "失败:$FAIL_COUNT"
|
|
echo "脚本目录:$OUTPUT_DIR"
|
|
echo "记忆文件:$MEMORY_FILE"
|
|
echo "============================================================"
|