Flyway 数据库版本管理完整实践指南
适配 Flyway 13.x(OSS 开源版),包含本地 Shell 脚本方案 + Maven / Gradle Java 项目集成,Mac M 系列开发踩坑总结。
一、什么是 Flyway
Flyway 是一款开源数据库版本迁移工具,把数据库变更(DDL/DML)当做代码一样管理,SQL 脚本纳入 Git 版本控制。
核心:基于 SQL 脚本驱动,简单直接,不需要写 Java 代码
自动维护一张
flyway_schema_history历史表,记录每一条脚本执行状态OSS 开源版没有 undo 回滚,回滚采用正向补偿脚本方案
支持命令行、Maven、Gradle、SpringBoot 多种方式
⚠️开源版限制:Undo 回滚、部分高级校验为商业 Pro 功能。
二、本地 Shell 命令行方案
1. 目录结构约定
flyway-demo/
├── db
│ └── migration #迁移脚本目录,固定
│ ├── V1__init_demo.sql
│ ├── V2__add_user_table.sql
│ └── R__view_demo.sql
├── drivers #存放JDBC驱动jar(mysql驱动)
├── flyway.conf #flyway配置文件
├── .env.mysql.dev #环境模板
└── flyway‑env.sh #环境加载别名脚本
2.SQL 脚本文件名规范
版本脚本 V 开头(最常用)
V<版本>__<描述>.sql,两个下划线分隔
V1__init_demo.sql
V2__add_user_table.sql
V3__alter_user_add_phone.sql
V 大写,版本号递增,不能重复
双下划线
__,单下划线识别失败已经执行成功的脚本禁止修改内容,修改会触发 checksum 校验失败
需要变更,新建更高版本脚本
可重复脚本 R 开头(视图 / 存储过程 / 函数)
R__view_user_stats.sql
无版本号;文件内容发生变化就会重复执行;不要用来做建表改表。
Undo 脚本
Uxx__:仅 Flyway Pro 商业版可用,开源版无效。
3. 脚本内部规范
文件编码:UTF‑8 无 BOM
多条语句使用分号
;结尾存储过程、触发器需要手动修改
DELIMITER分隔符
DELIMITER //
CREATE PROCEDURE sp_demo()
BEGIN
SELECT 1;
END //
DELIMITER ;
注释支持
--单行、/* */多行
4.5 个核心命令别名详解
先加载环境:
source ./flyway‑env.sh mysql dev
✅全新空白库:直接
fw‑migrate,不要 baseline。 ✅老数据库已有表:使用fw‑baseline打基线,跳过历史版本脚本。
5. 开源版如何回滚数据库
Flyway OSS 没有 undo 回滚命令,两种方案:
正向补偿脚本(推荐,行业标准) 不要修改旧 V 脚本,新建更高版本脚本抵消之前变更。 例:V2 创建 user 表,要撤销,新建
V3__drop_user_table.sql
DROP TABLE IF EXISTS `user`;
执行fw‑migrate完成回滚,所有变更留痕。
备份恢复(生产故障兜底) 上线前使用 mysqldump 备份数据库,故障时恢复快照。
#备份
mysqldump -uroot -p flyway_demo > backup.sql
#恢复
mysql -uroot -p flyway_demo < backup.sql
❌禁止:手动修改 / 删除
flyway_schema_history表记录;不要修改已经执行成功的 V 脚本。
6. 本地开发完整工作流
在
db/migration/新建迁移脚本fw‑info确认脚本识别,状态 Pendingfw‑migrate执行变更fw‑info确认全部 Successfw‑validate校验一致性
迁移失败排错流程:
fw‑info 看到状态 Failed
修改 SQL 修复问题
fw‑repair 清理失败标记
fw‑migrate 重新执行
三、Java 项目集成 Flyway
Flyway 会在应用启动时自动执行 db/migration 下的 SQL 脚本;也可以通过 Maven/Gradle 插件手动执行迁移,推荐开发环境使用插件,生产环境可应用启动自动执行。
前置说明
SpringBoot 项目引入 starter 即可;普通 Java 项目引入 flyway 核心包 + maven/gradle 插件。 脚本存放目录默认:src/main/resources/db/migration/
方案 1:Maven 集成(SpringBoot / 普通 Maven 项目)
① SpringBoot 项目(最简单)
maven pom.xml 引入 starter
<!-- flyway starter -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-spring-boot-starter</artifactId>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
application.yml配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/flyway_demo?useUnicode=true&characterEncoding=utf8mb4
username: root
password: "你的密码"
flyway:
enabled: true #开启flyway,应用启动自动migrate
baseline-on-migrate: false #全新库关闭;老库接入设置true自动baseline
locations: classpath:db/migration
validate-on-migrate: true
把迁移 SQL 脚本放到:
src/main/resources/db/migration/启动 SpringBoot 应用,Flyway 自动执行未运行脚本。
② Maven 插件方式(不依赖 SpringBoot,命令行执行迁移)
pom 增加 flyway‑maven‑plugin 插件
<build>
<plugins>
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>13.3.0</version>
<dependencies>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
<configuration>
<url>jdbc:mysql://127.0.0.1:3306/flyway_demo</url>
<user>root</user>
<password>你的密码</password>
<locations>classpath:db/migration</locations>
</configuration>
</plugin>
</plugins>
</build>
Maven 对应命令,等价前面 shell 别名:
#查看状态 等价 fw‑info
mvn flyway:info
#执行迁移 等价 fw‑migrate
mvn flyway:migrate
#校验脚本 等价 fw‑validate
mvn flyway:validate
#修复历史表 等价 fw‑repair
mvn flyway:repair
#基线 等价 fw‑baseline
mvn flyway:baseline
⚠️不要把密码硬编码 pom;可以使用 maven 属性、环境变量传入。
方案 2:Gradle 集成
① SpringBoot Gradle 项目 build.gradle
dependencies {
implementation 'org.flywaydb:flyway-spring-boot-starter:13.3.0'
runtimeOnly 'com.mysql:mysql-connector-j'
}
application.yml 配置和上面完全一致,启动应用自动迁移。
② Gradle 插件(命令手动执行迁移)
build.gradle
plugins {
id 'org.flywaydb.flyway' version '13.3.0'
}
flyway {
url = 'jdbc:mysql://127.0.0.1:3306/flyway_demo'
user = 'root'
password = '你的密码'
locations = ['classpath:db/migration']
}
dependencies {
flywayImplementation 'com.mysql:mysql-connector-j:8.0.33'
}
gradle 命令:
#查看状态
./gradlew flywayInfo
#执行迁移
./gradlew flywayMigrate
#校验
./gradlew flywayValidate
#修复
./gradlew flywayRepair
#基线
./gradlew flywayBaseline
四、生产环境最佳实践
SQL 脚本提交 Git,所有变更版本受控;禁止生产手动改库。
生产环境建议:CI 流水线执行 migrate,而不是应用启动自动迁移,避免多实例并发启动重复执行。
上线前执行 info + validate 校验通过,再执行 migrate。
上线前做好数据库备份。
老系统接入 Flyway,使用 baseline 打基线。
开源版回滚:使用正向补偿脚本,不依赖 undo。
五、Mac 开发踩坑清单(我们本次遇到的坑)
brew mysql@8.0 不要用
sudo brew services start,会破坏文件权限,报Bootstrap failed:5。Flyway13 配置变更:
flyway.driverJarDirs不能写在 conf 配置文件,只能命令行传参。MySQL dyld 库缺失是 brew 包依赖损坏,重装 mysql@8.0 即可。
MySQL 服务没启动,会报 Connection refused。
脚本文件名必须双下划线
V1__xxx.sql,单下划线无法识别。已经执行成功的脚本修改内容,validate 报 checksum mismatch。
六、常见问题总结
Validate failed Detected resolved migration not applied to database:本地存在 Pending 脚本,执行 migrate 即可。Unknown configuration property flyway.driverJarDirs:Flyway13 配置移除,改为命令行参数。Checksum mismatch:已经执行过的脚本被修改,禁止修改旧脚本,必要时执行 repair。baseline 什么时候用:老数据库已有业务表接入 Flyway;全新库不要 baseline。
附录:简化脚本
#!/bin/bash
set -euo pipefail
echo "====================================="
echo " Flyway 多数据库项目初始化脚本 (brew flyway13 macOS)"
echo " 支持: mysql | postgresql | clickhouse | duckdb "
echo "====================================="
# 可选数据库列表
SUPPORTED_DBS=("mysql" "postgresql" "clickhouse" "duckdb")
echo "请选择目标数据库:"
for i in "${!SUPPORTED_DBS[@]}"; do
echo " $((i+1)). ${SUPPORTED_DBS[$i]}"
done
read -p "输入序号选择: " DB_SELECT_IDX
DB_INDEX=$((DB_SELECT_IDX - 1))
DB_TYPE=${SUPPORTED_DBS[$DB_INDEX]}
read -p "开发环境数据库主机 [127.0.0.1]: " DB_HOST_DEV
DB_HOST_DEV=${DB_HOST_DEV:-127.0.0.1}
read -p "生产环境数据库主机: " DB_HOST_PROD
read -p "开发环境数据库端口: " DB_PORT_DEV
read -p "生产环境数据库端口: " DB_PORT_PROD
read -p "开发环境数据库名: " DB_NAME_DEV
read -p "生产环境数据库名: " DB_NAME_PROD
read -p "开发环境数据库用户: " DB_USER_DEV
read -p "生产环境数据库用户: " DB_USER_PROD
# 创建标准目录
mkdir -p db/migration
mkdir -p drivers
echo "[+] 创建目录 db/migration , drivers/"
# 生成示例迁移脚本
cat > db/migration/V1__init_demo.sql <<'EOF'
-- Flyway V1 初始化示例脚本
CREATE TABLE IF NOT EXISTS demo (
id INT AUTO_INCREMENT PRIMARY KEY,
remark VARCHAR(255),
create_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
EOF
echo "[+] 示例脚本 db/migration/V1__init_demo.sql"
# 根据数据库类型生成JDBC URL模板
case "${DB_TYPE}" in
mysql)
URL_DEV_TPL="jdbc:mysql://${DB_HOST_DEV}:${DB_PORT_DEV}/${DB_NAME_DEV}?useUnicode=true&characterEncoding=utf8mb4&useSSL=false"
URL_PROD_TPL="jdbc:mysql://${DB_HOST_PROD}:${DB_PORT_PROD}/${DB_NAME_PROD}?useUnicode=true&characterEncoding=utf8mb4"
;;
postgresql)
URL_DEV_TPL="jdbc:postgresql://${DB_HOST_DEV}:${DB_PORT_DEV}/${DB_NAME_DEV}"
URL_PROD_TPL="jdbc:postgresql://${DB_HOST_PROD}:${DB_PORT_PROD}/${DB_NAME_PROD}"
;;
clickhouse)
URL_DEV_TPL="jdbc:clickhouse://${DB_HOST_DEV}:${DB_PORT_DEV}/${DB_NAME_DEV}"
URL_PROD_TPL="jdbc:clickhouse://${DB_HOST_PROD}:${DB_PORT_PROD}/${DB_NAME_PROD}"
;;
duckdb)
URL_DEV_TPL="jdbc:duckdb:./${DB_NAME_DEV}.db"
URL_PROD_TPL="jdbc:duckdb:${DB_NAME_PROD}.db"
;;
*)
echo "不支持的数据库类型"
exit 1
;;
esac
ENV_DEV_TPL=".env.${DB_TYPE}.dev.template"
ENV_PROD_TPL=".env.${DB_TYPE}.prod.template"
# 生成开发环境模板
cat > "${ENV_DEV_TPL}" <<EOF
# ${DB_TYPE} DEV 环境模板
# 使用前复制: cp ${ENV_DEV_TPL} .env.${DB_TYPE}.dev
export FLYWAY_URL="${URL_DEV_TPL}"
export FLYWAY_USER="${DB_USER_DEV}"
export FLYWAY_PASSWORD=""
EOF
# 生成生产环境模板
cat > "${ENV_PROD_TPL}" <<EOF
# ${DB_TYPE} PROD 环境模板
# 使用前复制: cp ${ENV_PROD_TPL} .env.${DB_TYPE}.prod
export FLYWAY_URL="${URL_PROD_TPL}"
export FLYWAY_USER="${DB_USER_PROD}"
export FLYWAY_PASSWORD=""
EOF
echo "[+] 生成模板 ${ENV_DEV_TPL}"
echo "[+] 生成模板 ${ENV_PROD_TPL}"
# 主 flyway.conf,全部取自环境变量
cat > flyway.conf <<'EOF'
# ==================================
# Flyway 主配置文件
# 所有参数由 shell 环境变量注入
# ==================================
flyway.url=${FLYWAY_URL}
flyway.user=${FLYWAY_USER}
flyway.password=${FLYWAY_PASSWORD}
flyway.locations=filesystem:./db/migration
flyway.driverJarDirs=./drivers
flyway.baselineOnMigrate=true
flyway.validateOnMigrate=true
flyway.cleanDisabled=true
EOF
echo "[+] 生成 flyway.conf"
# 环境切换脚本 flyway‑env.sh
cat > flyway-env.sh <<'EOF'
#!/bin/bash
# 用法 source ./flyway-env.sh <dbtype> <env>
# 示例:
# source ./flyway-env.sh mysql dev
# source ./flyway-env.sh mysql prod
if [ $# -ne 2 ];then
echo "用法: source ./flyway-env.sh <dbtype> <dev|prod>"
echo "示例: source ./flyway-env.sh mysql dev"
exit 1
fi
DBTYPE="$1"
ENV="$2"
ENV_FILE="./.env.${DBTYPE}.${ENV}"
if [ ! -f "${ENV_FILE}" ];then
echo "❌ 环境文件不存在: ${ENV_FILE}"
echo "💡提示:请复制模板生成真实环境文件:"
echo "cp .env.${DBTYPE}.${ENV}.template ${ENV_FILE}"
echo "然后编辑填入 FLYWAY_PASSWORD"
exit 1
fi
echo "✅加载环境: db=${DBTYPE} , env=${ENV}"
set -a
source "${ENV_FILE}"
set +a
alias fw-info="flyway info"
alias fw-migrate="flyway migrate"
alias fw-validate="flyway validate"
alias fw-repair="flyway repair"
alias fw-baseline="flyway baseline -baselineVersion=1"
echo "已加载别名:"
echo " fw-info 查看迁移状态"
echo " fw-migrate 执行迁移"
echo " fw-validate 校验脚本"
echo " fw-repair 修复历史表"
echo " fw-baseline 基线初始化"
echo "💡直接输入 fw‑migrate 即可执行迁移"
EOF
chmod +x flyway-env.sh
echo "[+] 生成 flyway‑env.sh"
# gitignore
cat > .gitignore <<'EOF'
*.log
drivers/*.jar
# 真实环境文件忽略
.env.*
# 保留模板文件允许提交git
!.env*.template
EOF
echo "[+] 生成 .gitignore"
echo ""
echo "====================================="
echo "✅项目初始化完成"
find . -maxdepth 2
echo ""
echo "📋使用步骤:"
echo "1.复制模板生成真实环境文件:"
echo " cp ${ENV_DEV_TPL} .env.${DB_TYPE}.dev"
echo " cp ${ENV_PROD_TPL} .env.${DB_TYPE}.prod"
echo "2.编辑 .env.${DB_TYPE}.dev / .env.${DB_TYPE}.prod,填入 FLYWAY_PASSWORD"
echo "3.加载开发环境: source ./flyway‑env.sh ${DB_TYPE} dev"
echo "4.加载生产环境: source ./flyway‑env.sh ${DB_TYPE} prod"
echo "5.执行 fw‑info 查看状态,fw‑migrate 执行迁移"
echo ""
echo "⚠️注意事项"
echo " 1. JDBC驱动jar放到 ./drivers 目录"
echo " 2. 数据库实例需要预先手动创建,flyway不会创建database"
echo " 3. .env.* 文件已加入gitignore,禁止提交到代码仓库,仅 .*.template 可以提交"
echo "====================================="