当团队规模扩大、多个环境并行时,手工执行 SQL 改表往往带来「开发库改了、测试库忘了、生产库炸了」的灾难。Flyway 用一套版本化的迁移脚本,把数据库迁移纳入代码仓库与 CI/CD,让每一次 schema 变更都可追溯、可重复、可回滚。
一、为什么需要数据库版本管理
在没有版本化管理之前,数据库结构(schema)往往游离于代码之外:DBA 在图形化工具里手动 ALTER,开发在本地随手加字段,上线前再补一份 SQL 文件。这种模式在单人项目里勉强能跑,一旦遇到多环境、多人协作就会出现三类典型问题。
- 环境漂移:测试库比生产库多几张表,问题直到上线才暴露。
- 不可复现:无法从零重建出与生产一致的结构,新同事搭环境全靠口口相传。
- 无审计:谁在什么时候改了什么,完全说不清,出事只能靠回忆。
Flyway 的核心思想很简单:把每一次 schema 变更写成带版本号的迁移脚本,由工具按版本顺序自动执行。它像代码的版本控制一样,给数据库做「版本控制」。如果你已经在用 GitHub Actions 搭建 CI/CD 流水线,那么把迁移也交给流水线就是顺理成章的一步。
二、Flyway 核心概念
在动手之前,先理清四个关键概念,它们决定了 Flyway 的行为边界。
- Migration(迁移):一个独立的 schema 变更单元,分版本化迁移(Versioned)与可重复迁移(Repeatable)。
- Version(版本号):决定执行顺序,如 1、1.1、2。Flyway 会记录已执行的版本,只跑「未执行过的」。
- Checksum(校验和):脚本内容的哈希,防止已执行的脚本被偷偷改动导致不一致。
- Baseline(基线):对已有老库打基线,告诉 Flyway「这之前的版本我认了,从某个版本之后才开始管」。
2.1 迁移脚本命名规范
版本化脚本命名必须遵循 V{版本}__{描述}.sql,双下划线分隔版本与描述;可重复脚本以 R__描述.sql 开头。例如下面这组脚本,Flyway 会按 1 → 1.1 → 2 的顺序执行:
V1__create_user_table.sql
V1.1__add_user_email.sql
V2__create_order_table.sql
R__refresh_user_view.sql
2.2 Flyway 如何决定执行哪些脚本
Flyway 会在目标库里维护一张 flyway_schema_history 表,记录每次迁移的版本、文件名、校验和、执行时间。启动时它扫描脚本目录、比对历史表,只执行版本号大于已记录最大版本、且未被执行的脚本,从而保证幂等——重复跑不会重复建表。
三、Spring Boot 集成 Flyway
Spring Boot 对 Flyway 提供开箱即用的自动装配,几乎零配置即可启用。首先引入依赖(以 Maven 为例,PostgreSQL 需额外驱动模块):
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
然后在 application.yml 中配置数据源与 Flyway 行为:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/demo
username: demo
password: demo
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: true
out-of-order: false
把脚本放到 src/main/resources/db/migration 后,应用一启动 Flyway 就会自动执行。对 PostgreSQL 这类关系型库做 分区表优化 或 慢查询调优 时,结构化的迁移管理正是 Flyway 的用武之地。
四、编写第一个迁移脚本
创建 V1__create_user_table.sql,用纯 SQL 描述结构变更:
CREATE TABLE "user" (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(120),
created_at TIMESTAMP NOT NULL DEFAULT now()
);
CREATE INDEX idx_user_email ON "user" (email);
注意:PostgreSQL 默认把未加引号的标识符折叠为小写,表名用双引号包裹才能保留大写;这与 PostgreSQL 和 SQL Server 的语法差异 中强调的命名习惯一脉相承。执行后,flyway_schema_history 会多出一条 version=1 的记录,后续再部署这台机器就会跳过它。
五、Java 迁移与回调
除了 SQL,Flyway 还支持用 Java 编写迁移,适合需要编程逻辑的场景(如数据脱敏、批量转换、调用存储过程)。实现 JavaMigration 接口即可:
public class V3__seed_default_roles implements JavaMigration {
public void migrate(Context context) throws Exception {
try (Statement stmt = context.getConnection().createStatement()) {
stmt.execute("INSERT INTO role(name) VALUES ('ADMIN'),('USER')");
}
}
public MigrationVersion getVersion() { return MigrationVersion.fromVersion("3"); }
public String getDescription() { return "seed default roles"; }
}
回调(Callback)则能在迁移前/后插入钩子,比如迁移完成后自动刷新物化视图,或用 Docker Compose 拉起测试库时预置种子数据,让集成测试环境始终与生产结构对齐。
六、在 CI/CD 中自动迁移
把迁移交给流水线,而不是靠人工登录执行,是工程化的关键一步。下面是一段 GitHub Actions 片段,在部署前先跑迁移,失败则阻断发布:
name: deploy
on: [push]
jobs:
migrate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with: { java-version: '21', distribution: 'temurin' }
- run: mvn -q flyway:migrate
env:
FLYWAY_URL: ${{ secrets.DB_URL }}
FLYWAY_USER: ${{ secrets.DB_USER }}
FLYWAY_PASSWORD: ${{ secrets.DB_PWD }}
把数据库密码放进 Secrets、用最小权限账号执行迁移,既安全又可审计。配合预发环境先跑一遍,能挡掉绝大部分「本地能过、线上翻车」的结构问题。
七、生产环境避坑清单
迁移上生产有不少「看起来没事、一上就炸」的坑,整理成一张对照表,建议贴在团队 Wiki 上。
| 陷阱 | 现象 | 对策 |
|---|---|---|
| 改已执行的脚本 | checksum 不匹配,启动失败 | 永远新增脚本,不回头改旧版本 |
| 大表加列锁表 | 线上 ALTER 卡死、连接打满 | PG 11+ 多数 DDL 安全;MySQL 走 在线 DDL / pt-osc |
| 多环境顺序错乱 | out-of-order 误开导致跳版本 | 保持线性版本,关掉 out-of-order |
| 基线设置错误 | 重复执行老表创建报错 | 老库先 flyway baseline 再接管 |
八、Flyway vs Liquibase 选型
另一个常见选择是 Liquibase,两者定位相似但风格不同,选型时可以从这几个维度对比:
| 维度 | Flyway | Liquibase |
|---|---|---|
| 迁移描述 | 原生 SQL 为主,直观 | XML / YAML / JSON / SQL 多格式 |
| 跨库差异 | 贴近具体库方言 | 支持跨库抽象(changelog) |
| 回滚 | 需手写 undo 脚本 | 支持部分自动回滚 |
| 上手成本 | 低,约定优于配置 | 略高,配置更灵活 |
如果你的团队以 SQL 为主、追求简单可控,Flyway 是更轻的选择;若需要跨多种数据库、声明式管理,Liquibase 更合适。二者并不互斥,很多团队会按服务复杂度分别使用。
九、回滚策略与最佳实践
Flyway 社区版默认不自动回滚版本化迁移——这是有意为之,因为结构变更(如删列、改类型)的「反操作」往往不可逆或代价高昂。推荐三种回滚思路:
- 补偿式迁移:不删旧脚本,新增
Vn__undo_xxx.sql把变更抵消掉,保证前向可重放、历史可审计。 - 整库还原:依赖备份与 时序/快照机制,发布前先打快照,失败即回滚到快照。
- 蓝绿部署:迁移保持向后兼容(先加字段、双写、再删旧字段),与新版本代码并行运行。
最后给几条铁律:迁移脚本只增不改、保持线性版本、把执行权交给 CI、生产迁移先在预发验证。把这些纪律固化进流程,数据库就不再是上线最大的不确定性来源。
结语
Flyway 把一个容易被忽视的环节——数据库结构演进——变成了和代码一样可版本化、可审查、可自动化的资产。配合 CI/CD 与规范的命名约定,它能显著降低「结构漂移」带来的线上事故。下次要改表时,不妨先写一条 V{n}__描述.sql,让 Flyway 替你把风险管起来。




