数据库迁移:Flyway
本教程共 48 篇 · 第 29 篇 · 更新于 2026-08-13 · 约 6 分钟阅读
本节目标:理解为什么需要数据库迁移,会用 Flyway 管理版本化 SQL 脚本,并知道它和 schema.sql 的分工。
表结构怎么管:一个经典难题
代码有 Git 管版本,数据库结构没有。张三本地加了张表,李四不知道;线上库加了列,测试库还是旧的。最后每个环境的结构都不一样,代码一跑就炸。
手敲 SQL 改表更危险:今天改过的表,明天忘了在哪台机器执行过,再执行一遍就报错。更糟的是,线上库是团队共用资产,谁改过什么根本对不上账。
数据库迁移工具解决这个问题:所有结构变更写成脚本,按版本顺序执行,每个脚本只执行一次。结构变更从此有据可查、有版本可回,和代码一样进仓库。
Flyway 是什么
Flyway 是 Java 生态最流行的迁移工具,理念直白:把数据库当代码一样做版本控制。
它维护一张历史表(默认叫 flyway_schema_history),记录哪些脚本执行过。每次启动时扫描脚本目录,发现没执行过的新脚本就按版本号顺序执行,执行过的跳过。
工作流程拆开就三步:
- 扫描
db/migration目录,列出所有脚本。 - 读历史表,对比出没执行过的脚本。
- 按版本号从小到大逐个执行,并写入历史表。
Spring Boot 对 Flyway 的集成是开箱即用的:依赖一加,配置一写,启动时自动迁移。
引入依赖
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
用 MySQL 还要加数据库支持模块:
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
Note不同数据库要不同的 flyway 模块。PostgreSQL 加
flyway-database-postgresql,SQL Server 加对应模块。只加flyway-core连接不上 MySQL,这是常见坑。依赖不用写版本号,Spring Boot 的依赖管理(BOM)已经锁好兼容版本。
第一个迁移脚本
脚本放在 src/main/resources/db/migration/ 目录。命名有严格规范:
V1__create_book_table.sql
V2__add_price_column.sql
格式是 V<版本号>__<描述>.sql:大写 V、版本号、两个下划线、描述。版本号按数字递增,Flyway 按它排序执行。
V1__create_book_table.sql:
create table books (
id bigint auto_increment primary key,
title varchar(100) not null,
author varchar(50),
publish_date date
);
启动应用,Flyway 自动执行。日志会显示:
Successfully applied 1 migration to schema, now at version v1
去数据库看,books 表建好了,flyway_schema_history 表里多了这条记录。
脚本里不止能建表,索引、约束、视图都能写:
V1__create_book_table.sql(完整版):
create table books (
id bigint auto_increment primary key,
title varchar(100) not null,
author varchar(50),
publish_date date
);
create index idx_books_title on books(title);
一个迁移脚本可以放多条语句,Flyway 按 ; 分隔依次执行。
脚本写作有两条约定俗成的规矩:
- 一个脚本只做一件事:建表就建表,加列就加列。脚本越小,出错越好定位,回滚思路越清晰。
- 描述写清楚意图:
V2__add_price_column比V2__update有用一百倍。历史表里所有人靠描述理解这次变更。
版本号也不是越密越好。合并到主干时版本冲突了,把后合入的脚本版本号改大即可,Flyway 按最终版本排序执行。
升级:加第二个版本
需求来了,要加价格列。不能改 V1 的脚本,新建 V2:
V2__add_price_column.sql:
alter table books add column price decimal(10, 2);
重启应用,Flyway 发现 V2 没执行过,自动补上。团队其他人拉代码后启动,也会自动执行同样的脚本。
这就是版本化迁移的核心价值:结构变更跟着代码走,所有环境用同样的脚本达到同样的结构。
配置项
application.yml 里按需配置:
spring:
flyway:
enabled: true # 默认就是 true,可省略
locations: classpath:db/migration # 脚本目录,默认值
baseline-on-migrate: true # 数据库已有表时,跳过旧结构直接基线化
baseline-version: 0 # 基线版本
validate-on-migrate: true # 校验已执行脚本是否被改过
baseline-on-migrate 是接盘神器:项目接手一个已有数据的数据库,没有历史表。设为 true 后,Flyway 把当前结构记为基线版本 0,然后从 V1 开始往后执行,不会碰已有表。
还有两个配置按需了解:
spring.flyway.locations:脚本目录,默认classpath:db/migration,可以配多个目录用逗号分隔。spring.flyway.out-of-order:允许乱序版本(比如开发分支合并后出现 V3 晚于 V4)。生产默认关闭,别轻易开。
已执行脚本不能改
Flyway 会记录每个脚本的校验和。执行过的脚本内容被改动,校验和不匹配,启动直接报错:
Migration checksum mismatch for migration version 1
这是故意的安全机制:防止有人偷偷改历史脚本,导致各环境结构不一致。
Warning正确做法永远是「新增一个 V+N 脚本」,而不是修改旧的。改历史脚本等于篡改历史,测试环境也许能蒙混过关,生产环境迟早爆雷。
真改坏了(比如本地调试改过历史脚本),用
flyway repair命令重算校验和,但这只是修复元数据,结构差异还得自己补迁移脚本。
与 schema.sql 的取舍
很多人问:Flyway 和 schema.sql 都能建表,用哪个?
定位完全不同:
| 方式 | 定位 | 适合 |
|---|---|---|
| schema.sql/data.sql | 一次性初始化 | 开发环境造数据、演示项目 |
| Flyway | 持续演进的结构管理 | 所有要长期维护的项目 |
schema.sql 每次启动都可能重跑,没有版本概念,无法表达「从 V1 演进到 V2」的过程。Flyway 则是正式的迁移工具,脚本只执行一次,可追溯、可回滚思路清晰。
同类的迁移工具还有 Liquibase,用 XML/YAML 描述变更而不是纯 SQL,团队喜欢代码评审的话会选它。二选一即可,Flyway 上手更轻。
Tip用了 Flyway,就把
spring.jpa.hibernate.ddl-auto设为none或validate,结构交给 Flyway 管,别让 Hibernate 和迁移脚本抢着改表。两套机制同时管结构,早晚打架。
常见报错速查
| 报错 | 原因 | 处理 |
|---|---|---|
checksum mismatch | 改过已执行的脚本 | 新增脚本替代,别改旧的 |
non-empty schema | 库里有表但无历史表 | 开 baseline-on-migrate |
failed to connect | 数据源配置不对 | 检查 URL/账号/权限 |
| 找不到驱动模块 | 只加了 flyway-core | 补对应数据库模块 |
小结
Flyway 把表结构变更变成版本化脚本:V1 建表、V2 加列,每个脚本只执行一次,历史表记录一切。脚本只能新增不能修改,与 schema.sql 定位不同,长期项目选 Flyway。
下一节看 schema.sql 和 data.sql 的完整用法。