摘要:接手遗留系统,50+张表如何接入 Flyway?手动写脚本?NO!本文手把手教你一键导出DDL、智能清洗、基线接入,3分钟搞定百张表迁移,零数据风险!
📋 目录
为什么需要基线迁移?
场景还原:
- 你接手了一个运行2年的老系统
- 数据库有50+张表,结构复杂,外键交织
- 团队决定引入 Flyway 做版本管理
- 问题来了:难道要手写50个
CREATE TABLE脚本?
错误做法 ❌:
-- V1__users.sql
CREATE TABLE users (...);
-- V2__orders.sql
CREATE TABLE orders (...);
-- V3__products.sql
CREATE TABLE products (...);
-- 😱 还要写47个...
正确做法 ✅:
# 1条命令导出所有表结构
mysqldump --no-data mydb > V1__initial_schema.sql
# 1条命令基线接入
flyway -baselineVersion=1 -baselineOnMigrate=true migrate
# ✅ 完成!50张表纳入版本管理
核心流程总览
graph LR
A[现有数据库<br/>50+张表] --> B[一键导出DDL]
B --> C[智能清洗脚本]
C --> D[放入migration目录]
D --> E[基线接入]
E --> F[后续增量迭代]
style A fill:#f9f,stroke:#333,stroke-width:2px
style F fill:#9f9,stroke:#333,stroke-width:2px
关键原则:
- ✅ 不碰数据:只导出结构(
--no-data) - ✅ 不删表:移除所有
DROP TABLE语句 - ✅ 保留依赖:外键检查、字符集设置必须保留
- ✅ 一键基线:Flyway 自动标记,不重复执行
Step 1:一键导出DDL
MySQL 完整命令
mysqldump -u root -p \
--no-data \ # 只导出结构,不导出数据
--skip-add-drop-table \ # 不生成 DROP TABLE(关键!)
--skip-triggers \ # 不导出触发器(单独管理)
--no-create-db \ # 不生成 CREATE DATABASE
--skip-add-locks \ # 不加 LOCK TABLES
--complete-insert \ # 完整INSERT语句(如果有数据)
--routines=0 \ # 不导出存储过程
--events=0 \ # 不导出事件
mydb > V1__initial_schema.sql
PostgreSQL 完整命令
pg_dump -U postgres \
-s \ # 仅结构(schema only)
--no-owner \ # 不输出所有者命令
--no-privileges \ # 不输出权限命令
--schema=public \ # 指定schema
--exclude-table=flyway_schema_history \ # 排除Flyway元数据表
mydb > V1__initial_schema.sql
导出后检查清单
# 1. 查看文件大小(正常50张表约500KB-5MB)
ls -lh V1__initial_schema.sql
# 2. 统计表数量(应该≈50)
grep -c "^CREATE TABLE" V1__initial_schema.sql
# 3. 确认没有DROP语句
grep -i "DROP TABLE" V1__initial_schema.sql
# 应该无输出
# 4. 确认没有USE语句
grep "^USE " V1__initial_schema.sql
# 应该无输出
Step 2:智能清洗脚本
必须处理的5个地方
| 序号 | 必须处理项 | 原因 | 风险等级 |
|---|---|---|---|
| 1 | 删除 DROP TABLE IF EXISTS | 基线接入时表已存在,执行DROP会清空数据 | 🔴 致命 |
| 2 | 删除 CREATE DATABASE / USE db | Flyway已通过url连接目标库 | 🔴 致命 |
| 3 | 保留 SET FOREIGN_KEY_CHECKS=0/1 | 50张表有交叉外键,需临时关闭检查 | 🟢 必须保留 |
| 4 | 删除触发器/存储过程定义 | 这些应该用 R__ 脚本单独管理 | 🟡 建议删除 |
| 5 | 统一字符集为 utf8mb4 | 避免中文乱码和索引失效 | 🟡 建议统一 |
自动化清洗脚本(Linux/Mac)
创建 clean_flyway_sql.sh:
#!/bin/bash
# Flyway DDL 清洗脚本
# 用法:./clean_flyway_sql.sh V1__initial_schema.sql
INPUT_FILE=$1
OUTPUT_FILE=${INPUT_FILE%.sql}_cleaned.sql
if [ ! -f "$INPUT_FILE" ]; then
echo "❌ 文件不存在: $INPUT_FILE"
exit 1
fi
echo "🔧 开始清洗: $INPUT_FILE"
# 执行清洗
cat "$INPUT_FILE" | \
# 删除 DROP TABLE 语句
sed '/^DROP TABLE IF EXISTS/d' | \
# 删除 CREATE DATABASE 语句
sed '/^CREATE DATABASE/d' | \
# 删除 USE 语句
sed '/^USE /d' | \
# 删除触发器定义(从 CREATE TRIGGER 到结尾分号)
sed '/^CREATE.*TRIGGER/,/;$/d' | \
# 删除存储过程定义
sed '/^CREATE.*PROCEDURE/,/^END/d' | \
# 删除函数定义
sed '/^CREATE.*FUNCTION/,/^END/d' | \
# 删除注释中的敏感信息(TODO/FIXME)
sed '/--.*TODO/d' | \
sed '/--.*FIXME/d' | \
# 删除空行(超过3个连续空行压缩为1个)
sed '/^$/N;/^\n$/D' | \
# 统一字符集为 utf8mb4
sed 's/CHARSET=utf8/CHARSET=utf8mb4/g' | \
sed 's/COLLATE=utf8_bin/COLLATE=utf8mb4_unicode_ci/g' \
> "$OUTPUT_FILE"
# 在文件头部添加标准注释
cat > temp_header.sql << EOF
-- =====================================================
-- Flyway Migration: V1 - Initial Schema
-- Generated: $(date +%Y-%m-%d)
-- Database: MySQL
-- Tables: $(grep -c "^CREATE TABLE" "$OUTPUT_FILE")
--
-- WARNING: This is a baseline script.
-- Do NOT modify existing tables here.
-- Use V2__, V3__ for incremental changes.
-- =====================================================
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
EOF
# 在文件尾部添加恢复语句
cat >> temp_footer.sql << EOF
SET FOREIGN_KEY_CHECKS = 1;
EOF
# 合并文件
cat temp_header.sql "$OUTPUT_FILE" temp_footer.sql > "${OUTPUT_FILE%.sql}_final.sql"
# 清理临时文件
rm -f temp_header.sql temp_footer.sql "$OUTPUT_FILE"
echo "✅ 清洗完成: ${OUTPUT_FILE%.sql}_final.sql"
echo "📊 统计信息:"
echo " - 表数量: $(grep -c "^CREATE TABLE" "${OUTPUT_FILE%.sql}_final.sql")"
echo " - 文件大小: $(du -h "${OUTPUT_FILE%.sql}_final.sql" | cut -f1)"
echo " - 行数: $(wc -l < "${OUTPUT_FILE%.sql}_final.sql")"
使用方法:
chmod +x clean_flyway_sql.sh
./clean_flyway_sql.sh V1__initial_schema.sql
# 输出:
# 🔧 开始清洗: V1__initial_schema.sql
# ✅ 清洗完成: V1__initial_schema_cleaned_final.sql
# 📊 统计信息:
# - 表数量: 50
# - 文件大小: 1.2M
# - 行数: 3420
手动清洗检查清单
如果不想用脚本,至少手动检查这5点:
-- ✅ 文件头部应该有
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ❌ 不应该有这些(删除!)
DROP TABLE IF EXISTS `users`;
CREATE DATABASE IF NOT EXISTS `mydb`;
USE `mydb`;
-- ✅ 表定义应该完整保留
CREATE TABLE `users` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ✅ 文件尾部应该有
SET FOREIGN_KEY_CHECKS = 1;
Step 3:基线接入执行
目录结构
src/main/resources/
└── db/
└── migration/
├── V1__initial_schema.sql ← 清洗后的文件
├── V2__add_user_index.sql ← 后续增量脚本
├── V2_1__add_order_status.sql
└── R__refresh_report_views.sql ← 可重复脚本
执行基线命令
方式A:自动基线(推荐)
flyway \
-url=jdbc:mysql://localhost:3306/mydb \
-user=root \
-password=secret \
-locations=filesystem:./migrations \
-baselineVersion=1 \
-baselineOnMigrate=true \
migrate
方式B:手动基线(更严谨)
# 第一步:标记基线
flyway -baselineVersion=1 baseline
# 第二步:执行迁移(会跳过V1,从V2开始)
flyway migrate
验证结果
# 查看迁移状态
flyway info
预期输出:
+-----------+----------------------+----------+----------+---------------------+
| Version | Description | State | Type | Installed On |
+-----------+----------------------+----------+----------+---------------------+
| 1 | Initial schema | Success | BASELINE | 2026-04-08 10:30:15 |
+-----------+----------------------+----------+----------+---------------------+
检查数据库:
-- 1. 查看元数据表
SELECT * FROM flyway_schema_history;
-- 结果应该只有1条记录,version=1,state=Success
-- 2. 确认表结构未改变
SHOW TABLES;
-- 应该还是原来的50张表,没有被DROP
-- 3. 确认数据完整
SELECT COUNT(*) FROM users;
-- 数据量应该和迁移前一致
避坑指南
坑1:外键依赖报错
现象:
ERROR: Cannot add foreign key constraint
原因:表创建顺序错误,子表先于父表创建
解决方案:
-- ✅ 保留导出时的外键检查开关
SET FOREIGN_KEY_CHECKS = 0;
-- 所有 CREATE TABLE 语句(顺序无所谓)
CREATE TABLE child (... FOREIGN KEY (parent_id) REFERENCES parent(id));
CREATE TABLE parent (...);
SET FOREIGN_KEY_CHECKS = 1;
坑2:自增ID冲突
现象:新环境初始化后,插入数据报 Duplicate entry '1'
原因:基线脚本中 AUTO_INCREMENT=1,但业务数据已从100开始
解决方案:
# 方法1:导出后统一修改
sed -i 's/AUTO_INCREMENT=[0-9]*/AUTO_INCREMENT=10000/g' V1__initial_schema.sql
# 方法2:在脚本头部统一设置
cat >> V1__initial_schema.sql << EOF
-- 重置自增起点
ALTER TABLE users AUTO_INCREMENT=10000;
ALTER TABLE orders AUTO_INCREMENT=10000;
EOF
坑3:视图/存储过程混入
现象:基线执行失败,报权限不足或语法错误
原因:视图依赖的表还没创建,或存储过程需要特殊权限
解决方案:
✅ 正确做法:
- V1__initial_schema.sql:只放表/索引/约束
- R__create_views.sql:用可重复脚本管理视图
- R__create_procedures.sql:用可重复脚本管理存储过程
Flyway 会自动按顺序:
1. 执行 V1(建表)
2. 执行 R__(创建视图/过程)
坑4:字符集不一致
现象:中文乱码,或索引失效
解决方案:
-- 在脚本头部统一设置
SET NAMES utf8mb4;
SET CHARACTER SET utf8mb4;
-- 每个表明确指定
CREATE TABLE `users` (
...
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
坑5:Git 提交过大
现象:5MB 的 SQL 文件触发 CI 扫描慢,或 Git 历史膨胀
解决方案:
# 方案1:按模块拆分(适合>100张表)
V1_1__core_tables.sql # 用户、权限等核心表(10张)
V1_2__business_tables.sql # 订单、商品等业务表(30张)
V1_3__log_tables.sql # 日志、审计表(10张)
# 方案2:使用 Git LFS
git lfs track "*.sql"
git add V1__initial_schema.sql
git commit -m "Add baseline schema"
# 方案3:压缩后提交(不推荐,难Review)
gzip V1__initial_schema.sql
# 但 Flyway 不支持 .sql.gz,需解压后执行
自动化脚本
完整的一键迁移脚本
创建 migrate_baseline.sh:
#!/bin/bash
# Flyway 基线迁移一键脚本
# 用法:./migrate_baseline.sh mydb root password
set -e # 遇到错误立即退出
DB_NAME=$1
DB_USER=${2:-root}
DB_PASS=${3:-}
FLYWAY_URL="jdbc:mysql://localhost:3306/${DB_NAME}"
echo "🚀 开始 Flyway 基线迁移"
echo " 数据库: ${DB_NAME}"
echo " 用户: ${DB_USER}"
echo ""
# Step 1: 导出DDL
echo "📦 Step 1: 导出数据库结构..."
mysqldump -u "${DB_USER}" \
${DB_PASS:+-p"${DB_PASS}"} \
--no-data \
--skip-add-drop-table \
--skip-triggers \
--no-create-db \
--skip-add-locks \
--routines=0 \
--events=0 \
"${DB_NAME}" > V1__initial_schema.sql
TABLE_COUNT=$(grep -c "^CREATE TABLE" V1__initial_schema.sql)
echo " ✅ 导出完成,共 ${TABLE_COUNT} 张表"
# Step 2: 清洗脚本
echo "🔧 Step 2: 清洗SQL脚本..."
cat V1__initial_schema.sql | \
sed '/^DROP TABLE IF EXISTS/d' | \
sed '/^CREATE DATABASE/d' | \
sed '/^USE /d' | \
sed '/^CREATE.*TRIGGER/,/;$/d' | \
sed '/^CREATE.*PROCEDURE/,/^END/d' | \
sed 's/CHARSET=utf8[^ ]*/CHARSET=utf8mb4/g' \
> V1__initial_schema_clean.sql
# 添加头部注释
cat > V1__initial_schema.sql << EOF
-- =====================================================
-- Flyway Migration: V1 - Initial Schema
-- Database: ${DB_NAME}
-- Generated: $(date +%Y-%m-%d_%H:%M:%S)
-- Tables: ${TABLE_COUNT}
--
-- WARNING: This is a BASELINE script.
-- Do NOT modify existing tables here.
-- Use V2__, V3__ for incremental changes.
-- =====================================================
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
EOF
cat V1__initial_schema_clean.sql >> V1__initial_schema.sql
echo "SET FOREIGN_KEY_CHECKS = 1;" >> V1__initial_schema.sql
rm -f V1__initial_schema_clean.sql
echo " ✅ 清洗完成"
# Step 3: 移动到migration目录
echo "📁 Step 3: 移动到迁移目录..."
mkdir -p src/main/resources/db/migration
mv V1__initial_schema.sql src/main/resources/db/migration/
echo " ✅ 已放入 src/main/resources/db/migration/"
# Step 4: 执行基线
echo "🎯 Step 4: 执行Flyway基线..."
flyway \
-url="${FLYWAY_URL}" \
-user="${DB_USER}" \
${DB_PASS:+-password="${DB_PASS}"} \
-locations=filesystem:src/main/resources/db/migration \
-baselineVersion=1 \
-baselineOnMigrate=true \
migrate
echo ""
echo "🎉 基线迁移完成!"
echo ""
echo "后续操作:"
echo "1. 新增表结构变更 → 创建 V2__xxx.sql"
echo "2. 查看迁移状态 → flyway info"
echo "3. 验证数据完整性 → SELECT COUNT(*) FROM your_table"
使用方法:
chmod +x migrate_baseline.sh
./migrate_baseline.sh mydb root your_password
总结
核心要点回顾
- 一键导出:
mysqldump --no-data --skip-add-drop-table - 智能清洗:删除 DROP/USE/CREATE DATABASE,保留外键检查
- 基线接入:
flyway -baselineVersion=1 -baselineOnMigrate=true migrate - 零数据风险:基线只记录版本,不执行DDL(表已存在)
时间对比
| 方法 | 50张表耗时 | 风险 | 可维护性 |
|---|---|---|---|
| 手写50个脚本 | 2-3天 | 中(易漏表) | 差 |
| 导出后手动拼接 | 2小时 | 中(易出错) | 中 |
| 一键导出+清洗 | 5分钟 | 低(自动化) | 优 ✅ |
最佳实践清单
- ✅ 基线脚本只放表/索引/约束,视图/过程用 R__ 脚本
- ✅ 统一字符集为 utf8mb4
- ✅ 保留
FOREIGN_KEY_CHECKS=0/1包裹 - ✅ 生产环境设置
flyway.cleanDisabled=true - ✅ CI/CD 中强制
flyway validate
后续迭代建议
V1__initial_schema.sql ← 基线(已完成)
↓
V2__add_user_email_index.sql
V2_1__add_order_cancel_reason.sql
V3__refactor_payment_module.sql
↓
R__refresh_report_views.sql ← 可重复脚本
R__update_stored_procedures.sql