数据库版本管理这件事,很多人一开始都没当回事。代码进了 Git,接口文档有 Swagger,唯独数据库结构停留在“靠人传话”的阶段:谁改了表,就在群里说一句,或者丢个 sql 文件到共享目录。直到某次上线,生产环境的表漏加了一个字段,服务启动直接报错,一群人翻聊天记录才找到原因。后来我把 Flyway 集成进 Spring Boot 项目,才算是把这笔“烂账”彻底理顺了。
Flyway 是个数据库迁移工具,核心思路就是用一个统一目录管理所有数据库变更脚本,并且通过一张历史记录表控制哪些脚本执行过、哪些还没执行,按顺序自动补齐。对 Spring Boot 项目来说,它做得更彻底:应用启动时自动检查并执行迁移,几乎不用额外写触发代码。这篇文章就围绕 Spring Boot 集成 Flyway 的完整过程来写,包括依赖引入、配置参数、脚本规范、常见坑和排查思路,给需要的人一个可以直接照着做的参考。
我用的环境是 Spring Boot 2.7.18 + Flyway 8.5.13,如果你是 Spring Boot 3.x,版本对应关系会略有差异,下面会单独列出。文章里的内容基本都是我真实操作过的场景,不是只说“怎么配”,更重要的是解释“为什么这么配”。
1. 为什么要给数据库做版本管理
1.1 没有版本管理的数据库是什么状态
先说一个我接手过的项目。代码仓库里有两个分支,一个在开发新功能,一个在修线上 Bug。开发分支加了一张订单扩展表,线上 Bug 分支改了一个字段长度。等合并的时候,谁也不知道这两个变更是不是都执行过,DBA 手里有一堆零散的 sql 文件,文件名是 20240111_update.sql 这种,根本分不清哪个执行过、哪个没执行。
这种状态在中小团队里太常见了。没有数据库版本控制,直接带来的问题有三个:
- 变更记录靠人脑记忆,换个人就断档。
- 不同环境的数据库结构漂移,开发环境能跑,测试环境就挂。
- 并发开发时,两个人都改了同一张表,合并后结构对不上。
这三个问题的根子,就是把“数据库结构”当成了代码仓库之外的游离资产。而实际上,表结构、初始化数据、字段变更,都是应用的一部分,理应和代码一起版本化、可追溯、可回放。
1.2 Flyway 是怎么解决这个问题的
Flyway 的模型非常简单。你在 classpath:db/migration(默认位置)放一堆 SQL 脚本,脚本按约定命名,比如 V1__create_user_table.sql、V2__add_email_to_user.sql。第一次启动时,Flyway 会建一张名为 flyway_schema_history 的表,然后把执行过的脚本版本、校验值、执行时间都记进去。
以后每次应用启动,Flyway 会做两件事:
- 扫描 classpath 下的迁移脚本。
- 对比
flyway_schema_history表里已执行的记录,找出哪些是新脚本,按版本号顺序执行。
这样带来的好处是实打实的:同一套脚本在所有环境按相同顺序执行,不会漏,也不会重复。数据库结构从“薛定谔的猫”变成了可审计、可回放的状态。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与版本兼容问题
2.1 Spring Boot 与 Flyway 的版本对应关系
这一步是新手最容易卡住的,先说清楚。Spring Boot 不是自己实现了 Flyway,而是通过 spring-boot-dependencies 管理了 Flyway 的版本。不同 Spring Boot 版本对应的 Flyway 版本差异很大,直接决定了你能不能无脑加依赖。
我整理几个常用对应关系,这些是基于我实际验证过的。
| Spring Boot 版本 | Flyway 版本(默认依赖管理) | 需要注意的点 |
|---|---|---|
| 2.7.x | 8.5.x | 兼容 Flyway 8 和 9 的大多数写法,社区资料多 |
| 3.0.x | 9.16.x | 官方推荐新项目用这个组合,但 API 有调整 |
| 3.1.x | 9.22.x | 与 3.0 差异不大 |
| 3.2.x | 9.22.x | 注意 Java 17 要求,旧 JDK 8 项目升不动 |
这里有个很多人踩过的坑:如果你用 Spring Boot 3.x + Flyway 10.x(手动指定版本),Flyway 10 改了部分 API,并且默认不再支持 spring.flyway 下的一些旧属性,需要你额外引入 flyway-mysql 或 flyway-database-postgresql 模块。
2.2 Maven 依赖添加
我用的是 Spring Boot 2.7.18,Flyway 版本由 Boot 管理,所以只需要在 pom.xml 里加一个依赖:
xml复制<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
使用 MySQL 数据库时,Flyway 8.2 以后从 flyway-core 里拆分了数据库支持模块,所以还需要加 flyway-mysql 依赖,不然启动会报 Unsupported Database: MySQL。
xml复制<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
</dependency>
如果你用的是 PostgreSQL,则不需要额外的 flyway-mysql,但在 Flyway 10 版本下要加 flyway-database-postgresql。
如果 Spring Boot 默认管理的 Flyway 版本和你需要的不一致,也可以手动覆盖:
xml复制<properties>
<flyway.version>9.22.3</flyway.version>
</properties>
覆盖之前先确认目标 Flyway 版本能兼容你当前的数据库驱动和 Spring Boot 版本。我自己就遇到过:Boot 2.7.18 + Flyway 8.5.13 跑得好好的,为了用某个新功能升到 9.16,结果 MySQL 8.0 驱动和 Flyway 的兼容没问题,但代码里手动调用 Flyway.configure() 的 API 挂了,需要改方法名。所以没需求就别乱升,这是经验教训。
3. 让 Spring Boot 自动迁移的核心配置
3.1 application.yml 里的关键参数
Spring Boot 集成了 Flyway 的自动配置,引入了依赖之后,其实在绝大多数默认配置下项目就能跑。但要把这事做好,配置项必须门儿清。下面是我常用的配置模板:
yaml复制spring:
flyway:
enabled: true
locations: classpath:db/migration
encoding: UTF-8
baseline-on-migrate: true
baseline-version: 1
validate-on-migrate: true
clean-disabled: true
out-of-order: false
ignore-migration-patterns: "*:missing"
逐个说一下这些参数的实际含义,以及为什么要这么配。
1. baseline-on-migrate: true
这个是最重要的。如果不设置它,当你把一个已经存在数据库表的项目接入 Flyway 时,启动会报错:
code复制Migration checksum mismatch for migration version 1
或者更常见的是:
code复制FlywayException: Found non-empty schema(s) "test" but no schema history table.
意思很直白:数据库里已经有表了,但你没有任何版本记录,Flyway 不知道这些表是哪个版本创建的。设置了 baseline-on-migrate: true 后,Flyway 会把当前数据库状态标记为 baseline 版本(默认是 1),然后在 flyway_schema_history 表里插入一条 baseline 记录,之后再执行版本号大于 baseline 的迁移脚本。
2. baseline-version: 1
配合上一个参数使用,指定 baseline 对应的版本号。如果项目里已有的库结构对应你未来要写的 V2 脚本版本?你可以改成 baseline-version: 0 之类,具体看你的版本策略。默认值是 1,意味着 V1 的脚本不会再执行。
3. out-of-order: false
控制是否允许乱序执行迁移。生产环境我建议保持 false。开发环境如果需要,可以在本地临时改成 true,但切记提交前要改回来。
4. clean-disabled: true
强制禁止 Flyway 执行 clean 操作。clean 会删库跑路——把所有表都 drop 掉。在集成测试里这个功能有时候有用,但生产环境一旦被误触发就惨了。Spring Boot 2.7 里默认是 false 还是 true 我记不清了,稳妥起见直接写 true 锁死。
5. validate-on-migrate: true
校验已执行的脚本是否被改动过。大家自己改已经提交过的 SQL 文件,然后启动报 checksum mismatch,就是这个开关在起作用。生产环境必须开启,不然改了历史脚本你自己都不知道。
3.2 常见配置遗漏导致的启动失败
除了上面这些,有个初始配置经常被忽略——数据源。Flyway 默认使用 spring.datasource 配置的数据源,所以你如果只配了 spring.flyway.url 但是没配 spring.datasource 的一些属性,可能没问题,也可能挂,取决于你怎么搞。
还有编码问题,如果脚本里包含中文注释或中文字符串,spring.flyway.encoding: UTF-8 必须设置。默认就是 UTF-8,但如果你的 pom 里项目源码编码改过,或者操作系统默认编码有问题,还是要显式声明比较稳。
4. 迁移目录结构与脚本命名规范
4.1 一张图看懂 Flyway 目录
标准目录结构是这样:
code复制src/main/resources/
└── db/
└── migration/
├── V1__init_schema.sql
├── V1_1__add_user_email.sql
├── V1_2__add_user_phone.sql
├── V2__create_order_table.sql
└── R__user_statistics_view.sql
目录位置可以改,但默认值 classpath:db/migration 最省事。命名时注意大小写与版本号分隔符,Windows 文件系统对大小写不敏感,但 Linux 敏感,如果换环境跑要小心。
版本号的支持很灵活:V1、V1.1、V20240101 都可以。版本号仅支持数字和点号。V1.1.0 也是合法的。
4.2 V 版本迁移与 R 可重复迁移的区别
用多了会发现,V 开头的迁移脚本是“一次性”的,执行成功后,内容不允许再修改,否则会触发 checksum 校验失败。这符合正规的数据库版本管理理念:历史不可篡改,只能新增。
但日常工作中我经常遇到这类需求:一张统计视图的逻辑想调整,或者某些配置表的初始化数据想刷新一遍。每次都要新增一个 V 版本太蠢了,而且配置数据可能被手工改过,直接覆盖不合适。这时候就用 R 开头的“可重复迁移”。
sql复制-- R__user_order_daily_view.sql
CREATE OR REPLACE VIEW view_user_order_daily AS
SELECT ...
R 脚本不限制修改次数,只要内容变了,Flyway 下次启动就会重新执行。但有个坑:R 脚本的执行顺序在所有 V 脚本之后,而且多个 R 脚本之间会按文件名排序执行。如果 R 脚本的结果有依赖关系,命名要注意前缀,例如 R__domain_init_data.sql 和 R__domain_config.sql,按字典序执行。
5. 一个完整实操案例:从零接入
5.1 准备一个空的 Spring Boot 项目
假设你已经有了一个标准的 Spring Boot 项目,数据库连接配置没问题:
yaml复制spring:
datasource:
url: jdbc:mysql://localhost:3306/demo_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: root123
driver-class-name: com.mysql.cj.jdbc.Driver
然后引入依赖。上面已经写过 Maven 配置了。之后启动项目,如果没有在 db/migration 放任何脚本,Flyway 不会做任何事,不会报错也不会建表。
5.2 编写第一个迁移脚本
在 src/main/resources/db/migration 下创建 V1__init_user_table.sql:
sql复制CREATE TABLE `user` (
`id` BIGINT AUTO_INCREMENT PRIMARY KEY,
`username` VARCHAR(50) NOT NULL,
`email` VARCHAR(100),
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
INSERT INTO `user` (`username`, `email`) VALUES ('admin', 'admin@example.com');
启动项目,日志里会看到类似输出:
code复制INFO o.f.c.i.d.ConnectivityUtils - Successfully connected to the database
INFO o.f.c.i.s.JdbcTableSchemaHistory - Creating Schema History table `flyway_schema_history`
INFO o.f.c.i.s.JdbcTableSchemaHistory - Successfully applied 1 migration to schema `demo_db` (execution time 00:00.123s)
去数据库看,会发现多了 flyway_schema_history 表,里面有一条记录:
| installed_rank | version | description | type | script | checksum | success |
|---|---|---|---|---|---|---|
| 1 | 1 | init user table | SQL | V1__init_user_table.sql | 1234567890 | 1 |
此时如果再启动项目,Flyway 发现所有脚本都执行过,不会有任何变更。
5.3 新增第二个迁移脚本
随着需求迭代,需要给 user 表加一个 age 字段。正确做法是新建 V2 脚本,不要改 V1:
sql复制ALTER TABLE `user`
ADD COLUMN `age` INT NULL COMMENT '年龄' AFTER `email`;
再次启动,Flyway 会自动执行 V2,并记录新版本。整个过程没有手动跑任何 SQL,这就算入了门。
5.4 初始化既有数据库的处理方法
回到现实场景,你大概率是在一个“有数据结构的老项目”里引入 Flyway,而不是从空库开始。操作方法分两步:
- 先把数据库当前的 schema 导出成一份基线 SQL 文件,保存为
V1__baseline.sql。 - 应用设置
baseline-on-migrate: false时若数据库非空会报错,所以要设置baseline-on-migrate: true,同时baseline-version: 1。
注意:V1__baseline.sql 里放的是还没被 Flyway 管理的、现有数据库里的结构。但是如果你设置 baseline 版本为 1,那么 V1__baseline.sql 会不会执行?答案是:不会。因为 Flyway 会把 1 这个版本的记录标记为 already baselined,并且当你在目录里放了 V1__baseline.sql 时,还会产生冲突:Flyway 发现历史表里已有 baseline 版本 1,而脚本也存在 V1,校验时可能报错。
解决方式有两种:
第一种,baseline 版本设为一个不会和脚本冲突的号。比如你的新脚本从 V1 开始写,就把 baseline-version 设为 0:
yaml复制spring:
flyway:
baseline-on-migrate: true
baseline-version: 0
此时数据库当前已有的表被标记为 baseline 0,你从 V1 开始写任何脚本都会执行。
第二种,把现有数据库导成 V1 脚本,但不设置 baseline,让 Flyway 从 V1 开始跑。这种方式要求现有库是空的,或者你能接受版本记录里 V1 是“凭空创建”的。已经在生产环境跑的项目别这么干,团队里一旦有人本地库不太干净,直接启动失败。
我在实际项目里更常用“先导出现有 schema 作为 V1 创建脚本,然后 baseline-version 设置为 0,让 V1 在首次启动时执行补上历史表记录”。这个方式有个额外好处:新同事拉代码建空库,一条命令就能把历史结构全部建出来,不用找 DBA 要备份再恢复。
6. 实际运维中最常遇到的五种问题
6.1 checksum mismatch 校验失败
错误信息类似于:
code复制Validation failed: Migration checksum mismatch for migration version 2
原因就是有人把已经执行过的 V2 脚本改了。比如加了个注释,或者改了个字段默认值,checksum 就对不上了。Flyway 的本意是防篡改,但开发中偶尔会真的改错。
排查思路:用数据库客户端查历史表。
sql复制SELECT version, description, checksum, success FROM flyway_schema_history ORDER BY installed_rank;
对比当前文件内容和当时执行时的内容。但是你会发现历史表只存 checksum 不存原文,所以如果团队没有代码评审流程,这个问题只能靠 Git 历史来看脚本改了什么。一个规避手段是:在 merge request 时不要在旧版本 SQL 文件的改动上做无意义的注释修改。
如果确认某个变更确实不应该被当作历史修改(比如脚本里有语法错误,修了之后需要重跑),你有两条路:
- 用
flyway repair命令修正 checksum。 - Spring Boot 里可以通过
spring.flyway.repair-on-migrate配置吗?没有这个配置。需要自己调用 Flyway 的 repair API。
正常的修复手段是启动时调用 Flyway Repair,或者在命令行执行 mvn flyway:repair。
我个人的建议是:除非非常清楚后果,否则不要 repair。如果真的需要改历史脚本,多数情况下正确的姿势是新增一个 V 脚本,在新的脚本里重建或修改。比如上一个 V2 写错了,把字段长度设成 50,实际要 100,那就写 V3 去 alter。
6.2 Found non-empty schema but no schema history table
这个错误我在“初始化既有数据库”时说过,本质是数据库里有表,但没有历史表。解决方式就是配置 baseline:
yaml复制spring:
flyway:
baseline-on-migrate: true
不要一上来就执行 clean,也别手动删表。baseline 是为你这种场景设计的。
6.3 SQL 语法错误导致迁移中断
脚本执行失败时,Flyway 默认会回滚当前事务?实际上,DDL 语句在 MySQL 里不支持事务回滚。所以更准确地说,Flyway 不会执行失败的脚本,历史表里会记录一条 success=0 的记录。
这会产生一个连锁问题:下次启动,Flyway 检查到有失败记录,会直接拒绝继续执行,要求你先修复并 repair。
处理步骤:
- 手动修复当前的 SQL 语法错误。
- 启动前执行 flyway repair,删除或修正 failed 记录。
如果是开发环境,我有时候图省事会直接 truncate flyway_schema_history 并重新建。但这一步很危险,只能在自己的本地库这么做,生产环境千万别这么搞,否则会导致所有脚本被重新执行一遍,结果可能是各种冲突。
6.4 Spring Boot 3.x 多模块或多数据源的坑
多数据源场景下,Spring Boot 的自动配置只给主数据源配置了 Flyway。第二个数据源需要手动配置 Flyway 实例:
java复制@Configuration
public class SecondaryFlywayConfig {
@Bean
@ConfigurationProperties(prefix = "spring.flyway.secondary")
public FlywayProperties secondaryFlywayProperties() {
return new FlywayProperties();
}
}
大致思路是给每个数据源创造独立的 Flyway Bean,使用独立的 locations。多数据源项目的教训是:一定不要把 A 库的脚本路径写到 B 库上,否则 B 库会尝试执行 A 库的建表语句,报出一堆无意义的错误。
另外一个细节是,多模块 Maven 项目里如果每个模块都有 db/migration,依赖会合并 classpath,两个模块的同名 V1 脚本会造成冲突。这时候建议设置不同的前缀或者把脚本放到独立的模块里统一管理,再通过 spring.flyway.locations 指定扫描路径。
6.5 “Spring Boot 版本太高”导致的 Flyway 兼容问题
热词里有“springboot 版本太高”,这确实是个真问题。很多人新建项目用了 Spring Boot 3.4 甚至 3.5,然后引入 Flyway,启动时报各种奇奇怪怪的错。
Spring Boot 3.4 对应 Flyway 11,某版 3.5 对应 Flyway 11.x,这些新版本对 Java 版本、数据库支持模块有严格限制。如果项目本身有老的数据库代码或自定义方言,很可能会踩坑。
解决方向有三条:
- 跟随 Boot 管理版本,同时升级到 Java 17+。
- 手动指定 flyway 版本回退到 9.x,但要确认它能不能适配 Spring Boot 3,因为 Flyway 的自动化装配代码每年都有变化。
- 不使用 Spring Boot 自动装配,直接创建 Flyway 实例并手动调用 migrate。这个方法最绕,但能绕开版本装配不兼容。不到万不得已别走这条,工作量不成比例。
7. 生产环境与团队协作的最佳实践
7.1 脚本审核与不可变性原则
我参与过的团队,数据库脚本走的是和其他代码一样的 Code Review 流程。有一点需要额外注意:SQL 不只是在当前环境跑一遍就完事,它会在所有环境持续回放。所以脚本的写法必须考虑幂等性和兼容性。
比如加字段前先判断字段是否存在,这个方法在 MySQL 8.0 里不太好写,但你可以通过存储过程实现。总体上还是尽量简化脚本逻辑,避免依赖特定环境的前置数据。
7.2 迁移脚本和 Docker 部署的配合
不少项目会用 Docker 部署 Postgres/MySQL。Docker 重新创建数据库容器时,如果用了 volume 持久化,Flyway 历史表会跟着上次的状态走,正常不会重复执行。但如果开发过程中不小心删了 volume,数据库重建为空,Flyway 所有脚本会从零开始整库执行一遍,顺利的话表结构和数据会全部恢复,不会出问题,前提是你所有初始化脚本都经过了多次验证,能直接用。
但如果你之前的脚本里写了 INSERT INTO 且没有 ON DUPLICATE KEY UPDATE,而更新的脚本又依赖上一批运行后的数据状态,那么重建库时可能会蹦出一堆重复插入的报错。所以,每次写完迁移脚本,有条件的话都做一次“从空库跑全量脚本”的验证。这个动作很便宜,能提前发现大量依赖问题。
7.3 用 Flyway 做测试数据初始化
Flyway 除了管理 DDL,也经常用来灌基础数据,比如字典表、用户角色、系统配置。我的建议是分成两层:
- V 脚本放 DDL 和不可重复的基础数据,如建表、加索引。
- R 脚本放可以反复调整的配置类数据。
配置类数据如果在开发过程中改了,新同事拉代码后 R 脚本会自动更新,不用每次写“手动执行某条 update”的说明。
7.4 版本号设计的小建议
我见过团队版本号是这样写的:V20240101__xxx.sql,按日期走。这种策略有个隐性问题——如果同一天有两个提交,第二个提交的脚本版本号容易被误认为是下一天,而当天内如果重复使用同一个版本号,Flyway 启动直接报重复版本错误。
我倾向于用递进整数序列,V1、V2、V3 简单明了。冲突的时候让 Git 帮你解决,反正 merge 时会直观看到谁占了 V5。分支开发时,可以让每个开发者使用较大的“个人区段”,比如张三用 V1001 开头,李四用 V2001 开头,合并后再统一整理。不过这个做法在大型团队里才比较有必要,小团队直接递增即可。
8. 关于事务与执行顺序的几个疑问
8.1 Flyway 的执行是有事务保护的吗
Flyway 在执行每条迁移脚本时,会尝试开启事务。但对 MySQL 来说,DDL 是隐式提交的,一旦执行了 CREATE TABLE 或 ALTER TABLE,即使后续语句报错,前面的 DDL 也无法撤销。
这意味着,一个脚本里如果既有 DDL 又有 DML,而 DML 失败了,数据库结构已经变了,但 Flyway 会标记这条迁移为失败,下次启动时处于“卡住”状态。
规避方案:把 DDL 和 DML 拆分到不同脚本,或者在脚本里尽量让 DDL 排在前面、DML 放在后面,提前在预发环境跑通。更严谨的团队会要求每个脚本只做单一类型的变更,便于追踪和回滚。
8.2 V 脚本之间的执行顺序保证
Flyway 对 V 脚本的执行顺序有严格定义:
- 按版本号排序,
V1必须在V2前执行。 - 版本号相同时按描述字符串排序,但正常不会出现这种情况。
- 多个脚本之间不存在并行执行,永远是顺序执行。
版本号语义遵循“数字字符串排序”,不是字典序。所以 V1.10 会排在 V1.9 后面,这个顺序是合理的。
8.3 是否还需要手动执行 SQL
引入 Flyway 不代表 DBA 或开发就永远不需要手动执行 SQL 了。诸如数据订正、历史脏数据清洗这类操作,如果只是临时跑一次,可以先在 Flyway 里写好脚本,跑完再删掉,或者干脆允许 flyway_schema_history 保留失败记录?不太建议。
更推荐的做法:所有结构变更、初始化数据走 Flyway,临时性数据订正走专门的 SQL 审查流程,执行完在工单里留存。这样历史表里都是“可复用”的脚本,不会堆积一堆一次性操作。如果临时 SQL 也需要被追溯、保留,就写成一个 R 脚本,让它幂等地存在,而不是跑完即弃。
9. 额外补充:有没有更好的替代工具
比 Flyway 更早、也常被拿来对比的还有 Liquibase。Flyway 的优势是 SQL 原生简单直接,团队学习成本低;Liquibase 的卖点是数据库无关的 changelog 格式(XML/YAML/JSON),适合大型企业跨数据库类型迁移。
实际项目如果锁定在 MySQL 或 PostgreSQL,我一般推荐 Flyway。如果团队有多个数据库产品混用,Liquibase 的抽象层更有价值。但无论如何,二选一都比裸奔强——工具再简陋,也比“靠人传话”的数据库变更管理方式可靠得多。
最后分享一点实战感受
接手一个没有做过版本控制的 Spring Boot 项目时,最大的阻力往往不是技术,而是流程习惯的改变。以前 Dev 本地想改表就改表,想跑一条 SQL 就直接跑,接入 Flyway 之后,所有结构变更都要写脚本、走评审,初期会让人觉得很繁琐。但坚持跑一个月,效果是明显的:测试环境和生产环境的结构差异减少了,新人搭建环境的时间从半天缩到十分钟,线上因为表结构不一致造成的故障率直接清零。
根据我个人的经验,给刚准备接入 Flyway 的团队三个建议:不要把历史数据库的初始化脚本漏掉,不要试图改已经执行过的 V 脚本,不要在生产上关掉 validate 和 clean-disabled。这三点决定你后面会不会被 Flyway 反噬。工具本身不复杂,复杂的是团队能否养成“数据库变更必须随版本走”的纪律。把这套纪律立住了,后续再踩坑的次数就会少很多。
