483 lines
12 KiB
Markdown
483 lines
12 KiB
Markdown
# table-alias — 表结构对齐技能
|
||
|
||
**版本:** 1.1.1
|
||
**作者:** SQL Agent
|
||
**创建时间:** 2026-08-14
|
||
**最后更新:** 2026-08-14
|
||
|
||
---
|
||
|
||
## 重要注意事项
|
||
|
||
### ⚠️ `\n` 转义字符处理
|
||
|
||
**问题描述:**
|
||
|
||
MySQL `SHOW CREATE TABLE` 命令返回的建表语句中,字段定义之间使用**字面量** `\n` 字符(两个字符:反斜杠 + n),而非真正的换行符。这会导致生成的 SQL 脚本格式异常,无法直接执行。
|
||
|
||
**示例:**
|
||
```sql
|
||
-- 错误格式(包含字面量 \n)
|
||
CREATE TABLE `test` (
|
||
\n `id` int NOT NULL,\n `name` varchar(50),\n PRIMARY KEY (`id`)
|
||
)
|
||
```
|
||
|
||
**解决方案:**
|
||
|
||
在脚本中获取 `SHOW CREATE TABLE` 输出后,必须经过三步处理:
|
||
|
||
```bash
|
||
# 1. cut -f2:提取第二列(SHOW CREATE TABLE 返回 表名+CREATE 语句,制表符分隔)
|
||
# 2. sed 's/\\n/\n/g':将字面量 \n 转换为真实换行符
|
||
# 3. sed '1d;$d':删除首尾行,提取表体部分
|
||
SOURCE_CREATE=$(mysql ... -e "SHOW CREATE TABLE ..." | cut -f2 | sed 's/\\n/\n/g')
|
||
TABLE_BODY=$(echo "$SOURCE_CREATE" | sed '1d;$d')
|
||
```
|
||
|
||
**技术细节:**
|
||
|
||
1. **`cut -f2`** - `SHOW CREATE TABLE` 返回两列(表名、CREATE 语句),用制表符分隔,必须取第二列
|
||
|
||
```
|
||
# 原始输出格式:
|
||
table_name<TAB>CREATE TABLE `table_name` (\n `id` int...\n)
|
||
|
||
# 错误:tail -1 仍取整行
|
||
# 正确:cut -f2 只取 CREATE 语句列
|
||
```
|
||
|
||
2. **`sed 's/\\n/\n/g'`** - 将字面量 `\n`(两个字符)转换为真实换行符(一个字符)
|
||
|
||
```bash
|
||
# 注意转义:sed 中 \\n 匹配字面量 \n,\n 替换为真实换行
|
||
```
|
||
|
||
3. **`sed '1d;$d'`** - 删除第一行(`CREATE TABLE ... (`)和最后一行(`) ENGINE=...`),提取纯表体
|
||
|
||
```bash
|
||
# 替代失效的正则提取(sed 默认不跨行匹配)
|
||
# 1d = 删除第一行,$d = 删除最后一行
|
||
```
|
||
|
||
**已生成脚本的批量修复:**
|
||
|
||
如果脚本已生成但包含字面量 `\n`,使用以下命令批量修复:
|
||
|
||
```bash
|
||
cd /path/to/scripts
|
||
for f in *.sql; do sed -i 's/\\n/\n/g' "$f"; done
|
||
```
|
||
|
||
**验证方法:**
|
||
|
||
打开 SQL 文件,检查 `CREATE TABLE` 语句:
|
||
- ✅ 正确:字段定义分布在多行,格式清晰
|
||
- ❌ 错误:所有字段定义在一行,包含 `\n` 字符
|
||
|
||
---
|
||
|
||
## 功能描述
|
||
|
||
将**准生产数据库**的表结构同步到**VPS 开发库**,确保开发环境与准生产环境表结构一致。
|
||
|
||
**同步方向:** 准生产(只读)→ VPS(root 可写)
|
||
|
||
**支持场景:**
|
||
- 新建表:目标库不存在该表时,执行 `CREATE TABLE`
|
||
- 修改表:目标库已存在该表时,智能对比并执行 `ALTER TABLE`
|
||
- 批量同步:支持同步整个数据库的所有表
|
||
|
||
---
|
||
|
||
## 触发方式
|
||
|
||
**自然语言命令:**
|
||
|
||
```
|
||
同步准生产 dmp_serp 的表结构到 VPS
|
||
同步准生产 dmp_smdm 的表结构到 VPS
|
||
```
|
||
|
||
**参数解析:**
|
||
- 源数据库:固定为"准生产"
|
||
- 源库名:从命令中提取(如 `dmp_serp`)
|
||
- 目标数据库:固定为"VPS"
|
||
- 目标库名:默认与源库名相同
|
||
|
||
---
|
||
|
||
## 配置文件
|
||
|
||
**位置:** `/home/yangxuan/.openclaw/workspace-sql/.db-sync-config.yaml`
|
||
|
||
**格式:**
|
||
|
||
```yaml
|
||
# 源数据库配置(准生产 - 只读)
|
||
sources:
|
||
- name: 准生产
|
||
host: 100.115.195.188
|
||
port: 50036
|
||
user: witsoftd
|
||
password_env: MYSQL_PWD_WIT
|
||
readonly: true
|
||
description: "准生产环境,只读权限"
|
||
|
||
# 目标数据库配置(VPS - root)
|
||
targets:
|
||
- name: VPS
|
||
host: 101.34.227.188
|
||
port: 3306
|
||
user: root
|
||
password_env: MYSQL_PWD_SQL
|
||
readonly: false
|
||
description: "VPS 开发环境,root 权限"
|
||
# 禁止同步的数据库黑名单
|
||
blacklist:
|
||
- ruoyi-vue-pro
|
||
- mysql
|
||
- sys
|
||
- performance_schema
|
||
|
||
# 输出配置
|
||
output:
|
||
base_dir: /home/yangxuan/codes/xuan-sql/scripts
|
||
date_subdir: true
|
||
write_memory: true
|
||
```
|
||
|
||
---
|
||
|
||
## 执行流程
|
||
|
||
### 1. 解析命令
|
||
|
||
从自然语言命令中提取:
|
||
- 源库名(如 `dmp_serp`)
|
||
- 目标库名(默认与源库名相同)
|
||
|
||
### 2. 读取配置
|
||
|
||
从 `.db-sync-config.yaml` 读取:
|
||
- 源数据库连接信息(准生产)
|
||
- 目标数据库连接信息(VPS)
|
||
- 输出路径配置
|
||
|
||
### 3. 验证连接
|
||
|
||
```bash
|
||
# 验证源库连接(只读)
|
||
mysql -h <source_host> -P <source_port> -u <source_user> -p"${PASSWORD}" -e "SELECT 1;"
|
||
|
||
# 验证目标库连接(root)
|
||
mysql -h <target_host> -P <target_port> -u <target_user> -p"${PASSWORD}" -e "SELECT 1;"
|
||
```
|
||
|
||
### 4. 获取表列表
|
||
|
||
从源数据库获取所有表:
|
||
|
||
```sql
|
||
SHOW TABLES FROM <source_database>;
|
||
```
|
||
|
||
### 5. 对比并生成脚本
|
||
|
||
对每个表执行:
|
||
|
||
#### 5.1 读取源表结构
|
||
|
||
```sql
|
||
SHOW CREATE TABLE <source_database>.<table_name>\G
|
||
```
|
||
|
||
#### 5.2 读取目标表结构
|
||
|
||
```sql
|
||
-- 检查表是否存在
|
||
SHOW TABLES FROM <target_database> LIKE '<table_name>';
|
||
|
||
-- 如果存在,读取结构
|
||
SHOW CREATE TABLE <target_database>.<table_name>\G
|
||
```
|
||
|
||
#### 5.3 智能对比
|
||
|
||
**对比维度:**
|
||
|
||
| 维度 | 对比方式 | 处理策略 |
|
||
|------|---------|---------|
|
||
| 表是否存在 | `SHOW TABLES` | 不存在 → `CREATE TABLE` |
|
||
| 字段列表 | 对比 `information_schema.COLUMNS` | 缺失 → `ADD COLUMN` |
|
||
| 字段类型 | 对比 `COLUMN_TYPE` | 不同 → `MODIFY COLUMN` |
|
||
| NULL 约束 | 对比 `IS_NULLABLE` | 不同 → `MODIFY COLUMN` |
|
||
| 默认值 | 对比 `COLUMN_DEFAULT` | 不同 → `MODIFY COLUMN` |
|
||
| 注释 | 对比 `COLUMN_COMMENT` | 不同 → `MODIFY COLUMN` |
|
||
| 索引 | 对比 `information_schema.STATISTICS` | 差异 → `DROP/ADD INDEX` |
|
||
| 主键 | 对比 `COLUMN_KEY` | 差异 → `DROP/ADD PRIMARY KEY` |
|
||
|
||
**对比算法:**
|
||
|
||
```python
|
||
# 伪代码
|
||
def compare_tables(source_ddl, target_ddl):
|
||
changes = []
|
||
|
||
# 1. 目标表不存在 → CREATE
|
||
if target_ddl is None:
|
||
return ['CREATE', source_ddl]
|
||
|
||
# 2. 解析字段列表
|
||
source_fields = parse_fields(source_ddl)
|
||
target_fields = parse_fields(target_ddl)
|
||
|
||
# 3. 对比字段
|
||
for field in source_fields:
|
||
if field.name not in target_fields:
|
||
changes.append(['ADD COLUMN', field])
|
||
elif field != target_fields[field.name]:
|
||
changes.append(['MODIFY COLUMN', field])
|
||
|
||
# 4. 删除多余字段(目标有,源没有)
|
||
for field_name in target_fields:
|
||
if field_name not in source_fields:
|
||
changes.append(['DROP COLUMN', field_name])
|
||
|
||
# 5. 对比索引(略)
|
||
|
||
return changes
|
||
```
|
||
|
||
#### 5.4 生成 SQL 脚本
|
||
|
||
**单表脚本格式:**
|
||
|
||
```sql
|
||
-- ============================================================
|
||
-- 表结构同步脚本:{table_name}
|
||
-- 源:准生产 ({source_host}:{source_port})
|
||
-- 目标:VPS ({target_host}:{target_port}) {target_database}
|
||
-- 生成时间:{timestamp}
|
||
-- ============================================================
|
||
|
||
USE {target_database};
|
||
|
||
-- 如果表不存在,创建表
|
||
CREATE TABLE IF NOT EXISTS {table_name} (
|
||
...
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||
|
||
-- 如果表已存在,修改结构
|
||
-- 添加缺失字段
|
||
ALTER TABLE {table_name} ADD COLUMN {field_name} {type} ...;
|
||
|
||
-- 修改字段类型
|
||
ALTER TABLE {table_name} MODIFY COLUMN {field_name} {type} ...;
|
||
|
||
-- 删除多余字段
|
||
ALTER TABLE {table_name} DROP COLUMN {field_name};
|
||
|
||
-- 添加索引
|
||
ALTER TABLE {table_name} ADD INDEX {index_name} ({columns});
|
||
|
||
-- ============================================================
|
||
-- 验证
|
||
-- ============================================================
|
||
-- SHOW CREATE TABLE {table_name}\G
|
||
```
|
||
|
||
### 6. 执行脚本
|
||
|
||
对每个生成的脚本:
|
||
|
||
```bash
|
||
# 执行单表脚本
|
||
mysql -h <target_host> -u <target_user> -p"${PASSWORD}" -D <target_database> < <script_file>
|
||
|
||
# 捕获错误,继续执行下一个表
|
||
if [ $? -ne 0 ]; then
|
||
echo "ERROR: Failed to sync ${table_name}"
|
||
log_error "${table_name}: ${error_message}"
|
||
else
|
||
echo "OK: Synced ${table_name}"
|
||
log_success "${table_name}"
|
||
fi
|
||
```
|
||
|
||
### 7. 记录日志
|
||
|
||
写入 `memory/{date}.md`:
|
||
|
||
```markdown
|
||
## 表结构同步 - {timestamp}
|
||
|
||
**源:** 准生产 `{source_database}`
|
||
**目标:** VPS `{target_database}`
|
||
**处理表数:** {count}
|
||
**成功:** {success_count}
|
||
**失败:** {fail_count}
|
||
|
||
### 处理的表
|
||
|
||
| 表名 | 操作 | 状态 | 说明 |
|
||
|------|------|------|------|
|
||
| table1 | CREATE | ✅ | 新建表 |
|
||
| table2 | ALTER | ✅ | 添加 3 个字段 |
|
||
| table3 | ALTER | ❌ | 字段类型冲突 |
|
||
|
||
### 脚本位置
|
||
|
||
```
|
||
{output_dir}/{date}/
|
||
├── README.md
|
||
├── {database}__{table1}__struct.sql
|
||
├── {database}__{table2}__struct.sql
|
||
└── ...
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## 错误处理
|
||
|
||
**策略:** 遇到错误时,记录日志并继续处理下一个表。
|
||
|
||
**常见错误及处理:**
|
||
|
||
| 错误 | 原因 | 处理 |
|
||
|------|------|------|
|
||
| `ERROR 1138: Invalid use of NULL value` | 字段有 NULL 值,无法改为 NOT NULL | 先更新 NULL 值为默认值,再修改 |
|
||
| `ERROR 1060: Duplicate column name` | 字段已存在 | 跳过,记录警告 |
|
||
| `ERROR 1054: Unknown column` | 字段不存在 | 跳过,记录错误 |
|
||
| `ERROR 1171: All parts of a PRIMARY KEY must be NOT NULL` | 主键字段为 NULL | 记录错误,需人工处理 |
|
||
| 连接超时 | 网络问题 | 重试 3 次,失败则跳过 |
|
||
|
||
---
|
||
|
||
## 安全限制
|
||
|
||
### 1. 源数据库限制
|
||
|
||
- 固定为"准生产"(`100.115.195.188:50036`)
|
||
- 只读权限,无法执行 DDL
|
||
|
||
### 2. 目标数据库限制
|
||
|
||
- 固定为"VPS"(`101.34.227.188:3306`)
|
||
- 黑名单数据库禁止同步:
|
||
- `ruoyi-vue-pro`
|
||
- `mysql`
|
||
- `sys`
|
||
- `performance_schema`
|
||
|
||
### 3. 表数量限制
|
||
|
||
- 单次同步表数量 > 50 时,需用户确认
|
||
|
||
---
|
||
|
||
## 输出产物
|
||
|
||
### 1. SQL 脚本文件
|
||
|
||
**路径:** `/home/yangxuan/codes/xuan-sql/scripts/{YYYYMMDD}/`
|
||
|
||
**命名:** `{database}__{table_name}__struct.sql`
|
||
|
||
### 2. README.md
|
||
|
||
```markdown
|
||
# 表结构同步脚本 - {date}
|
||
|
||
**源:** 准生产 `{source_host}:{source_port}` `{source_database}`
|
||
**目标:** VPS `{target_host}:{target_port}` `{target_database}`
|
||
**生成时间:** {timestamp}
|
||
**执行状态:** ✅ 已完成 / ⚠️ 部分失败
|
||
|
||
## 脚本清单
|
||
|
||
| 序号 | 脚本文件 | 操作类型 | 状态 |
|
||
|------|---------|---------|------|
|
||
| 1 | `{database}__{table1}__struct.sql` | CREATE TABLE | ✅ |
|
||
| 2 | `{database}__{table2}__struct.sql` | ALTER TABLE | ✅ |
|
||
| ... | ... | ... | ... |
|
||
|
||
## 执行结果
|
||
|
||
- 总表数:{total}
|
||
- 成功:{success}
|
||
- 失败:{fail}
|
||
|
||
## 验证命令
|
||
|
||
```sql
|
||
SHOW TABLES LIKE '{database_pattern}';
|
||
SHOW CREATE TABLE {table_name}\G
|
||
```
|
||
```
|
||
|
||
### 3. 记忆文件
|
||
|
||
**路径:** `memory/{date}.md`
|
||
|
||
**内容:** 执行摘要、表清单、脚本位置
|
||
|
||
---
|
||
|
||
## 示例
|
||
|
||
### 示例 1:同步 dmp_serp
|
||
|
||
**命令:**
|
||
```
|
||
同步准生产 dmp_serp 的表结构到 VPS
|
||
```
|
||
|
||
**执行:**
|
||
1. 读取配置:准生产 → VPS
|
||
2. 获取 `dmp_serp` 所有表(如 50 张)
|
||
3. 对每个表:
|
||
- 对比结构差异
|
||
- 生成 SQL 脚本
|
||
- 执行脚本
|
||
4. 写入 `memory/2026-08-14.md`
|
||
|
||
**输出:**
|
||
```
|
||
/home/yangxuan/codes/xuan-sql/scripts/20260814/
|
||
├── README.md
|
||
├── dmp_serp__dmp_om_productinfo__struct.sql
|
||
├── dmp_serp__dmp_om_productinfo_cost__struct.sql
|
||
└── ...
|
||
```
|
||
|
||
### 示例 2:同步 dmp_smdm
|
||
|
||
**命令:**
|
||
```
|
||
同步准生产 dmp_smdm 的表结构到 VPS
|
||
```
|
||
|
||
**执行:** 同上,目标库为 `dmp_smdm`
|
||
|
||
---
|
||
|
||
## 相关文件
|
||
|
||
- **技能目录:** `/home/yangxuan/.openclaw/workspace-sql/skills/table-alias/`
|
||
- **配置文件:** `/home/yangxuan/.openclaw/workspace-sql/.db-sync-config.yaml`
|
||
- **脚本输出:** `/home/yangxuan/codes/xuan-sql/scripts/`
|
||
- **记忆文件:** `/home/yangxuan/.openclaw/workspace-sql/memory/`
|
||
|
||
---
|
||
|
||
## 版本历史
|
||
|
||
| 版本 | 日期 | 变更 |
|
||
|------|------|------|
|
||
| 1.1.2 | 2026-08-14 | 补充 `\n` 转义字符处理的技术细节:`cut -f2` 提取第二列、`sed '1d;$d'` 提取表体 |
|
||
| 1.1.1 | 2026-08-14 | 修复 `\n` 转义字符问题:在 `SHOW CREATE TABLE` 输出处理中添加 `sed 's/\\n/\n/g'` 转换 |
|
||
| 1.1.0 | 2026-08-14 | 支持单表模式,避免全量同步的耗时和风险 |
|
||
| 1.0.0 | 2026-08-14 | 初始版本 |
|