1. 背景
项目设计难免有不到位的时候,在重构时可能表的字段/外键/索引等会发生变动,寻常的.sql迁移文件固然可用,但这种约束难免会有错漏的时候,比如说同事A已经修改了数据结构但是忘了改表结构,或者改了表结构但是完全没有留档为什么改,关联哪个功能。
Flyway主要解决的就是数据库表迁移的规范以及流程上的问题。
2. 命名规范
对于每一次的表变动,要学会使用类似于这样的命名规则:
V1__init.sql
版本号放前面,后面跟着具体做的事情。
3. 使用 Flyway 执行 .sql 文件
Flyway 的核心使用方式不是手动去数据库客户端执行 .sql,而是把迁移文件放到约定目录中,由 Flyway 统一扫描、执行和记录。
在 Spring Boot 项目中,常见目录为: src/main/resources/db/migration。
应用启动时,如果开启了 Flyway,Spring Boot 会自动触发 Flyway 执行迁移。执行成功后,Flyway 会在数据库中维护一张历史表: flyway_schema_history
这张表用于记录每个迁移脚本是否执行过、执行时间、checksum、执行结果等信息。
如果你执行了一次后将这个V1的SQL文件进行了修改,还想着再执行一次,Flyway会直接报checksum的错。
4. 常用命令
4.1 migrate
执行尚未运行过的迁移脚本:
flyway migrate4.2 info
查看当前数据库迁移状态,包括已执行、未执行、失败的迁移:
flyway info4.3 validate
校验当前迁移文件是否和历史记录一致:
flyway validate如果某个已经执行过的SQL文件被修改,通常会在这里暴露 checksum 错误
4.4 repair
修复 Flyway 历史表中的校验信息或者失败记录。
flyway repair这个命令不能随便使用,需要先确认为什么会出现 checksum 或者失败记录。
5. 使用原则
- 已经执行到测试环境或者生产环境的
V版本迁移文件不能再修改。 - 后续变更通过新增版本文件完成,比如说
V2__add_user_status.sql。 - 正式
DDL、索引、初始化数据应该通过Flyway管理。 - 临时查询或者排查
SQL可以自己在数据库中手动执行,不过不要绕过Flyway去动表结构。 - 上线前应该执行
validate,确认迁移文件和数据库历史纪录一致。
6. 常见错误
比如说修改已经执行过的V版本文件,这个是过不了checksum校验的,表中已经记录过之前执行时的 checksum信息。
手动执行SQL后又让Flyway执行,可能会出现 Table already exists之类的异常。
命名不符合之前说的规范,Flyway会识别不了。
随意使用 repair,这跟使用TypeScript却又无脑使用any没什么区别。
上面这些就比较常见。
6.1 多人同时创建相同版本号
严格地来说,其实这个应该是属于CI上应该解决的问题,有重复的话就提醒开发者对应文件版本冲突。
只是依赖git协作规范的话,很有可能遇到类似于事务中会碰到的问题──推送前都确认过没有冲突版本,但是两人时间差隔着很小的话,就会出现冲突。
不过也可以使用V+时间戳的命名方案,这样能极大概率减少版本冲突,虽然并不为0。
6.2 在重要环境中使用clean
或者说,这种命令不应该被允许使用,需要权限控制。
6.2.1 通过配置禁用
Flyway 提供了 cleanDisabled 配置,用于控制是否允许执行 clean。
在重要环境中应开启禁用:
spring: flyway: clean-disabled: true开启后,即使有人误触发 clean,Flyway 也会拒绝执行。
6.2.2 数据库账号权限限制
这个是运维层面的问题了,简单地说就是生产环境或者测试环境的应用数据库账号不授予高危权限,比如说:
- DROP
- CREATE
- ALTER(视项目迁移策略决定)
- 删除
schema或者database的权限
而且如果需要在发布时执行DDL,迁移账号可能需要一定的DDL权限,可能会这样分:
- 应用运行账号:只保留业务运行所需权限
- 迁移发布账号:只在发布阶段使用,受审批/流水线控制。
6.2.3 限制执行入口
CI/CD或发布脚本中,不要暴露执行flyway clean的入口。
7. 已有数据库接入 Flyway
如果项目一开始没有使用 Flyway,数据库中已经存在表结构,那么不能简单地直接加入 V1__init.sql 后就运行migrate。
因为数据库已经不是空库,Flyway 不知道这些表是如何产生的,也没有对应的历史记录。
这时需要使用 baseline。
7.1 baseline 的含义
baseline 的作用是告诉 Flyway:
当前数据库已经存在一批历史结构,从某个版本开始,把它作为迁移基线。基线之前的迁移不再重复执行,后续只执行更高版本的迁移。
例如,当前数据库已经有完整的初始表结构,可以将它标记为版本 1: baselineVersion = 1
之后 Flyway 会从 V2__xxx.sql 开始执行。
7.2 接入步骤
先整理一份baseline SQL。
可以将当前已有表结构导出为:V1__baseline.sql
这份文件用于记录项目的初始结构,方便新环境从零创建数据库。
然后对已有数据库执行 baseline。
已有数据库已经有表了,所以不应该让 Flyway 再执行 V1__baseline.sql。
应该让 Flyway 记录当前库已经处于版本 1。
后续变更从 V2 开始。
例如:
V2__add_contract_status.sql
V3__create_approval_table.sql
7.3 baselineOnMigrate
Flyway 支持配置:baselineOnMigrate=true
它的含义是:当执行 migrate 时,如果发现目标 schema 非空,并且还没有 flyway_schema_history,就自动执行 baseline。
这个配置虽然方便,但要谨慎使用。
原因是它会降低安全保护。如果配置了错误的数据源,本来 Flyway 应该因为“非空库且无历史表”而拒绝执行,但开启 baselineOnMigrate 后,它可能会自动把错误的库标记为 baseline。
因此,生产环境建议显式执行 baseline,不要随意开启自动 baseline。
7.4 接入后的原则
- 已有数据库接入
Flyway后,后续结构变更统一通过新的版本迁移文件完成 - 基线版本之前的历史
SQL不再重复执行 - 新环境可以从
V1__baseline.sql开始完整建库 - 已有环境通过
baseline标记当前版本,然后从后续版本继续迁移 - 不要为了让
Flyway通过而删除历史表或随意修改flyway_schema_history
8. 可重复迁移
除了V开头的版本迁移以外,还有R开头的可重复迁移文件。
这类文件没有版本号,命名格式通常是:R__描述.sql
Repeatable Migration 的特点是:第一次会执行,之后如果文件内容发生变化,Flyway 检测到 checksum 变化,会再次执行。
它适合放一些“可以被重新定义”的数据库对象,例如:
- 视图
- 函数
- 存储过程
- 可重复刷新的基础数据
例如:
CREATE OR REPLACE VIEW user_summary ASSELECT id, username, statusFROM users;9. 总结
如果是表结构演进,例如建表、加字段、加索引,优先使用 V 文件: V3__add_user_email.sql
如果是可以被整体替换的定义,例如视图、函数、存储过程,可以考虑使用 R 文件:R__user_summary_view.sql
不要把普通表结构变更放进 R 文件中。因为 R 文件可能因为内容变化被重新执行,如果里面写的是:
ALTER TABLE ADD COLUMN
重复执行时很容易失败。
简单来说,V 是有版本的一次性迁移,R 是内容变化后会重新执行的迁移。
另外还有U开头的撤销迁移,但不推荐使用,其功能在社区版受限(需要企业版),而且在生产环境自动回滚DDL风险很高。
一般来说,哪怕是撤销,也应该是使用V做新版本迁移,而非U。
10. 推荐工作流
一次数据库变更通常应该按以下流程进行:
- 根据业务需求新增迁移文件,例如
V202606081030__add_contract_status.sql - 在迁移文件中编写明确、可重复理解的
DDL或初始化数据 - 本地启动应用或执行
flyway migrate,确认迁移可以成功执行 - 执行
flyway info,确认迁移状态符合预期 - 执行
flyway validate,确认迁移文件与历史记录一致 - 将迁移文件和对应业务代码一起提交
- 合并前由 CI 执行迁移校验
- 发布时由受控流程在目标环境执行迁移
需要注意的是,数据库迁移文件不是业务代码的附属品,而是数据库结构演进的正式记录。
只要某次功能变更依赖表结构、索引、外键或初始化数据变化,就应该把对应迁移文件纳入同一次代码变更中。