一、Flyway是什么
官网解释地非常全面,可先大致阅读一下。
简单地说,flyway是一个能对数据库变更做版本控制的工具。
二、为什么要用Flyway
在多人开发的项目中,我们都习惯了使用SVN或者Git来对代码做版本控制,主要的目的就是为了解决多人开发代码冲突和版本回退的问题。
其实,数据库的变更也需要版本控制,在日常开发中,我们经常会遇到下面的问题:
- 自己写的SQL忘了在所有环境执行;
- 别人写的SQL我们不能确定是否都在所有环境执行过了;
- 有人修改了已经执行过的SQL,期望再次执行;
- 需要新增环境做数据迁移;
- 每次发版需要手动控制先发DB版本,再发布应用版本;
- 其它场景...
有了flyway,这些问题都能得到很好的解决。
三、如何使用Flyway
3.1 准备数据库
首先,我们需要准备好一个空的数据库。(数据库的安装和账密配置此处忽略)
此处以mysql为例,在本地电脑上新建一个空的数据库,名称叫做flyway,我们通过dbeaver看到的样子如下:
新建一个空的数据库
3.2 准备SpringBoot工程
在start.spring.io上新建一个SpringBoot工程,要求能连上自己本地新建的mysql数据库flyway,这个步骤也比较简单,就不再细讲。
但要注意的是,application.yml中数据库的配置务必配置正确,下述步骤中系统启动时,flyway需要凭借这些配置连接到数据库。这里贴一份:
server:
port: 8080
spring:
application:
name: flyway-demo
datasource:
druid: # 使用druid数据源
url: jdbc:mysql://127.0.0.1:3306/flyway?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
filters: stat # 配置监控统计拦截的filters
initial-size: 10 # 初始化大小,最小,最大
min-idle: 8
max-active: 20
max-wait: 60000 # 配置获取连接等待超时的时间
time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位是毫秒
validation-query: select 'x'
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-open-prepared-statements: 20
stat-view-servlet:
login-username: admin #druid监控用户名
login-password: admin #druid监控密码
flyway:
# 是否启用flyway
enabled: true
## 编码格式,默认UTF-8
encoding: UTF-8
## 迁移sql脚本文件存放路径,默认db/migration
locations: classpath:db/migration
## 迁移sql脚本文件名称的前缀,默认V
sqlMigrationPrefix: V
## 迁移sql脚本文件名称的分隔符,默认2个下划线__
sqlMigrationSeparator: __
# 迁移sql脚本文件名称的后缀
sqlMigrationSuffixes: .sql
# 迁移时是否进行校验,默认true
validateOnMigrate: true
# 设置为true,当迁移发现数据库非空且存在没有元数据的表时,自动执行基准迁移,新建schema_version表
baselineOnMigrate: true
相关属性
flyway.baseline-description 对执行迁移时基准版本的描述.
flyway.baseline-on-migrate 当迁移时发现目标schema非空,而且带有没有元数据的表时,是否自动执行基准迁移,默认false.
flyway.baseline-version 开始执行基准迁移时对现有的schema的版本打标签,默认值为1.
flyway.check-location 检查迁移脚本的位置是否存在,默认false.
flyway.clean-on-validation-error 当发现校验错误时是否自动调用clean,默认false.
flyway.enabled 是否开启flywary,默认true.
flyway.encoding 设置迁移时的编码,默认UTF-8.
flyway.ignore-failed-future-migration当 读取元数据表时是否忽略错误的迁移,默认false.
flyway.init-sqls 当初始化好连接时要执行的SQL.
flyway.locations 迁移脚本的位置,默认db/migration.
flyway.out-of-order 是否允许无序的迁移,默认false.
flyway.password 目标数据库的密码.
flyway.placeholder-prefix 设置每个placeholder的前缀,默认${.
flyway.placeholder-replacementplaceholders 是否要被替换,默认true.
flyway.placeholder-suffix 设置每个placeholder的后缀,默认}.
flyway.placeholders.[placeholder name] 设置placeholder的value
flyway.schemas 设定需要flywary迁移的schema,大小写敏感,默认为连接默认的schema.
flyway.sql-migration-prefix 迁移文件的前缀,默认为V.
flyway.sql-migration-separator 迁移脚本的文件名分隔符,默认__
flyway.sql-migration-suffix 迁移脚本的后缀,默认为.sql
flyway.tableflyway 使用的元数据表名,默认为schema_version
flyway.target 迁移时使用的目标版本,默认为latest version
flyway.url 迁移时使用的JDBC URL,如果没有指定的话,将使用配置的主数据源
flyway.user 迁移数据库的用户名
flyway.validate-on-migrate 迁移时是否校验,默认为true
多数据源时需要配置(如读写分离)
需要配置 url、user、password,如使用shardingsphere框架
flyway:
url: ${spring.shardingsphere.datasource.master.jdbc-url}
user: ${spring.shardingsphere.datasource.master.username}
password: ${spring.shardingsphere.datasource.master.password}
3.3 flyway的引入与尝试
首先,在pom文件中引入flyway的核心依赖包:
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>${flyway.version}</version>
</dependency>
其次,在src/main/resources目录下面新建db.migration文件夹,默认情况下,该目录下的.sql文件就算是需要被flyway做版本控制的数据库SQL语句。
但是此处的SQL语句命名需要遵从一定的规范,否则运行的时候flyway会报错。命名规则主要有两种:
- 仅需要被执行一次的SQL命名以大写的"V"开头,后面跟上"0~9"数字的组合,数字之间可以用“.”或者下划线"_"分割开,然后再以两个下划线分割,其后跟文件名称,最后以.sql结尾。比如,
V2.1.5__create_user_ddl.sql
、V4.1_2__add_user_dml.sql
。 - 可重复运行的SQL,则以大写的“R”开头,后面再以两个下划线分割,其后跟文件名称,最后以.sql结尾。。比如,
R__truncate_user_dml.sql
。
其中,V开头的SQL执行优先级要比R开头的SQL优先级高。
如下,我们准备了三个脚本,分别为:
-
V1__create_user.sql
,其中代码如下,目的是建立一张user表,且只执行一次。CREATE TABLE IF NOT EXISTS `USER`( `USER_ID` INT(11) NOT NULL AUTO_INCREMENT, `USER_NAME` VARCHAR(100) NOT NULL COMMENT '用户姓名', `AGE` INT(3) NOT NULL COMMENT '年龄', `CREATED_TIME` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `CREATED_BY` varchar(100) NOT NULL DEFAULT 'UNKNOWN', `UPDATED_TIME` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `UPDATED_BY` varchar(100) NOT NULL DEFAULT 'UNKNOWN', PRIMARY KEY (`USER_ID`) )ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
V2__add_user.sql
,其中代码如下,目的是往user表中插入一条数据,且只执行一次。insert into `user`(user_name,age) values('lisi',33);
-
R__add_unknown_user.sql
,其中代码如下,目的是每次启动倘若有变化,则往user表中插入一条数据。insert into `user`(user_name,age) values('unknown',33);
与之相对应的目录截图如下:
项目目录结构
其中2.1.6、2.1.7和every的文件夹不会影响flyway对SQL的识别和运行,可以自行取名和分类。
到这一步,flyway的默认配置已经足够我们开始运行了。此时,我们启动SpringBoot的主程序,如果以上步骤没有配置错误的话,运行如下:
[INFO ] 2022-04-02 15:58:51.846 [main] o.f.c.i.license.VersionPrinter - Flyway Community Edition 7.15.0 by Redgate
[INFO ] 2022-04-02 15:58:51.847 [main] o.f.c.i.d.base.BaseDatabaseType - Database: jdbc:mysql://127.0.0.1:3306/flyway (MySQL 5.7)
[INFO ] 2022-04-02 15:58:51.899 [main] o.f.core.internal.command.DbValidate - Successfully validated 3 migrations (execution time 00:00.024s)
[INFO ] 2022-04-02 15:58:51.919 [main] o.f.c.i.s.JdbcTableSchemaHistory - Creating Schema History table `flyway`.`flyway_schema_history` ...
[INFO ] 2022-04-02 15:58:51.977 [main] o.f.core.internal.command.DbMigrate - Current version of schema `flyway`: << Empty Schema >>
[INFO ] 2022-04-02 15:58:51.987 [main] o.f.core.internal.command.DbMigrate - Migrating schema `flyway` to version "1 - create user"
[INFO ] 2022-04-02 15:58:52.026 [main] o.f.core.internal.command.DbMigrate - Migrating schema `flyway` to version "2 - add user"
[INFO ] 2022-04-02 15:58:52.044 [main] o.f.core.internal.command.DbMigrate - Migrating schema `flyway` with repeatable migration "add unknown user"
[INFO ] 2022-04-02 15:58:52.060 [main] o.f.core.internal.command.DbMigrate - Successfully applied 3 migrations to schema `flyway`, now at version v2 (execution time 00:00.092s)
[INFO ] 2022-04-02 15:58:52.126 [main] o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8080"]
flyway成功运行
此时,我们刷新数据库,可以看到flyway的历史记录表已经生成并插入了三个版本的记录:
flyway_schema_history
而且,user表也已经创建好了并插入了两条数据:
我们不改变任何东西,再次执行主程序,日志如下:
[INFO ] 2022-04-02 16:02:13.863 [main] o.f.c.i.license.VersionPrinter - Flyway Community Edition 7.15.0 by Redgate
[INFO ] 2022-04-02 16:02:13.864 [main] o.f.c.i.d.base.BaseDatabaseType - Database: jdbc:mysql://127.0.0.1:3306/flyway (MySQL 5.7)
[INFO ] 2022-04-02 16:02:13.930 [main] o.f.core.internal.command.DbValidate - Successfully validated 3 migrations (execution time 00:00.035s)
[INFO ] 2022-04-02 16:02:13.942 [main] o.f.core.internal.command.DbMigrate - Current version of schema `flyway`: 2
[INFO ] 2022-04-02 16:02:13.943 [main] o.f.core.internal.command.DbMigrate - Schema `flyway` is up to date. No migration necessary.
两张数据库表中的内容也毫无任何变化。
可是,如果我们修改V2__add_user.sql
中的内容,再次执行的话,就会报错,提示信息如下:
[ERROR] Migration checksum mismatch for migration version 2
如果我们修改了R__add_unknown_user.sql
,再次执行的话,该脚本就会再次得到执行,并且flyway的历史记录表中也会增加本次执行的记录。
3.4 Flyway 的规则
Flyway 是如何比较两个 SQL 文件的先后顺序呢?它采用 采用左对齐原则, 缺位用 0 代替 。举几个例子:
1.0.1.1 比 1.0.1 版本高。
1.0.10 比 1.0.9.4 版本高。
1.0.10 和 1.0.010 版本号一样高, 每个版本号部分的前导 0 会被忽略。
Flyway 将 SQL 文件分为 Versioned 、Repeatable 和 Undo 三种:
- Versioned 用于版本升级, 每个版本有唯一的版本号并只能执行一次.
- Repeatable 可重复执行, 当 Flyway检测到 Repeatable 类型的 SQL 脚本的
checksum
有变动, Flyway 就会重新应用该脚本. 它并不用于版本更新, 这类的migration
总是在 Versioned 执行之后才被执行。 - Undo 用于撤销具有相同版本的版本化迁移带来的影响。但是该回滚过于粗暴,过于机械化,一般不推荐使用。一般建议使用 Versioned 模式来解决。
这三种的命名规则如下图:
- Prefix 可配置,前缀标识,默认值
V
表示 Versioned,R
表示 Repeatable,U
表示 Undo - Version 标识版本号, 由一个或多个数字构成, 数字之间的分隔符可用点
.
或下划线_
- Separator 可配置, 用于分隔版本标识与描述信息, 默认为两个下划线
__
- Description 描述信息, 文字之间可以用下划线
_
或空格 分隔 - Suffix 可配置, 后续标识, 默认为
.sql
四、maven 插件
每次想要migration都需要运行整个springboot项目,并且只能执行migrate一种命令,其实flyway还是有很多其它命令的。maven插件给了我们不需要启动项目就能执行flyway各种命令的机会。
在pom中引入flyway的插件,同时配置好对应的数据库连接。
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>${flyway.version}</version>
<configuration>
<url>jdbc:mysql://localhost:3306/flyway?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT</url>
<user>root</user>
<password>123456</password>
<driver>com.mysql.cj.jdbc.Driver</driver>
</configuration>
</plugin>
然后更新maven插件列表,就可以看到flyway的全部命令了。
此时,我们双击执行上图中的flyway:migrate的效果和启动整个工程执行migrate的效果是一样的。
其它命令的作用如下列出,各位可自行实验体会:
migrate
Migrate是指把数据库Schema迁移到最新版本,是Flyway工作流的核心功能,Flyway在Migrate时会检查Metadata(元数据)表,如果不存在会创建Metadata表,Metadata表主要用于记录版本变更历史以及Checksum之类的。
baseline
Baseline针对已经存在Schema结构的数据库的一种解决方案,即实现在非空数据库中新建Metadata表,并把Migrations应用到该数据库。
Baseline可以应用到特定的版本,这样在已有表结构的数据库中也可以实现添加Metadata表,从而利用Flyway进行新Migrations的管理了。
clean(慎用)
Clean相对比较容易理解,清除掉对应数据库Schema中所有的对象,包括表结构,视图,存储过程等,clean操作在dev 和 test阶段很好用,但在生产环境务必禁用。
info
Info用于打印所有Migrations的详细和状态信息,其实也是通过Metadata表和Migrations完成的,Info能够帮助快速定位当前的数据库版本,以及查看执行成功和失败的Migrations。下图很好地示意了Info打印出来的信息。
repair
repair操作能够修复Metadata表,该操作在Metadata表出现错误时是非常有用的。
Repair会修复Metadata表的错误,通常有两种用途:
移除失败的Migration记录,该问题只是针对不支持DDL事务的数据库。
重新调整已经应用的Migratons的Checksums值,比如:某个Migratinon已经被应用,但本地进行了修改,又期望重新应用并调整Checksum值,不过尽量不要这样操作,否则可能造成其它环境失败。
undo
撤销操作,社区版不支持。
validate
Validate是指验证已经Apply的Migrations是否有变更,Flyway是默认是开启验证的。
Validate原理是对比Metadata表与本地Migrations的Checksum值,如果值相同则验证通过,否则验证失败,从而可以防止对已经Apply到数据库的本地Migrations的无意修改。
flyway补充知识
- flyway执行migrate必须在空白的数据库上进行,否则报错;
- 对于已经有数据的数据库,必须先baseline,然后才能migrate;
- clean操作是删除数据库的所有内容,包括baseline之前的内容;
- 尽量不要修改已经执行过的SQL,即便是R开头的可反复执行的SQL,它们会不利于数据迁移;
五、旧项目如何接入
5.1 执行baseline
>mvn baseline
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------< com.ybw:flyway-demo >-------------------------
[INFO] Building flyway-demo 1.0.0
[INFO] --------------------------------[ jar ]---------------------------------
[WARNING] The POM for com.alibaba:druid:jar:1.2.6 is invalid, transitive dependencies (if any) will not be available, enable debug logging for more details
[INFO]
[INFO] --- flyway-maven-plugin:7.15.0:baseline (default-cli) @ flyway-demo ---
[INFO] Flyway Community Edition 7.15.0 by Redgate
[INFO] Database: jdbc:mysql://localhost:3306/flyway (MySQL 5.7)
[INFO] Schema history table `flyway`.`flyway_schema_history` already initialized with (1,<< Flyway Baseline >>). Skipping.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.446 s
[INFO] Finished at: 2022-04-02T16:50:42+08:00
[INFO] ------------------------------------------------------------------------
会创建flyway_schema_history表,初始化一条数据
影响
会把1版本占用,版本1sql就不会执行了。
5.2 执行migrate
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------< com.ybw:flyway-demo >-------------------------
[INFO] Building flyway-demo 1.0.0
[INFO] --------------------------------[ jar ]---------------------------------
[WARNING] The POM for com.alibaba:druid:jar:1.2.6 is invalid, transitive dependencies (if any) will not be available, enable debug logging for more details
[INFO]
[INFO] --- flyway-maven-plugin:7.15.0:migrate (default-cli) @ flyway-demo ---
[INFO] Flyway Community Edition 7.15.0 by Redgate
[INFO] Database: jdbc:mysql://localhost:3306/flyway (MySQL 5.7)
[INFO] Successfully validated 5 migrations (execution time 00:00.025s)
[INFO] Current version of schema `flyway`: 1
[INFO] Migrating schema `flyway` to version "2 - add user"
[INFO] Migrating schema `flyway` to version "3 - add user"
[INFO] Migrating schema `flyway` to version "4 - add user"
[INFO] Migrating schema `flyway` with repeatable migration "add unknown user"
[INFO] Successfully applied 4 migrations to schema `flyway`, now at version v4 (execution time 00:00.113s)
[INFO] Flyway Community Edition 7.15.0 by Redgate
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.451 s
[INFO] Finished at: 2022-04-02T16:52:42+08:00
[INFO] ------------------------------------------------------------------------
flyway_schema_history生成新纪录
5.3 结论
- 旧项目如果想记录以前的表结构、数据,可以放到版本1的sql里面。
- 旧项目如果不用记录以前的表结构、数据,也可以不执行baseline,直接从1版本开始,新的ddl和dml。
6、团队使用
比如当前有两个团队A、B,2个版本在开发1.1.0、1.2.0,每个版本后再加一位数(第四位数),保证不同时间、不同人更新,版本号根据时间更新。
团队 | 研发人员 | 时间 | 版本 |
A团队开发 | A1开发 | 2021年5月1日 | V1.1.0.1__create_user.sql |
A2开发 | 2021年5月1日 | V1.1.0.2__create_person.sql | |
A1开发 | 2021年5月2日 | V1.1.0.3__create_staff.sql | |
B团队开发 | B1开发 | 2021年5月1日 | V1.2.0.1__create_order.sql |
B2开发 | 2021年5月1日 | V1.2.0.2__create_goods.sql | |
B1开发 | 2021年5月2日 | V1.2.0.3__create_student.sql |
版本号是根据时间升序,所以第四位数字可以是时间。
7、其他
注释
纠错
如果在开发过程中,需要修改V1.1.0.1的sql,当前最大版本为1.1.0.5
- 将flyway_schema_history对应的1.1.0.1记录删除。
- 将V1.1.0.1的sql文件改为V1.1.0.6。
flyway和Archery
两者结合使用。
- flyway管理版本,ddl处理。
- archery负责无版本上线,如不改表结构dml的的临时上线。
名词解释
DDL(Data Definition Language 数据定义语言)
Create语句:可以创建数据库和数据库的一些对象。
Drop语句:可以删除数据表、索引、触发程序、条件约束以及数据表的权限等。
Alter语句:修改数据表定义及属性。
DML(Data Manipulation Language 数据操控语言)
Insert语句:向数据表张插入一条记录。
Delete语句:删除数据表中的一条或多条记录,也可以删除数据表中的所有记录,但是,它的操作对象仍是记录。
Update语句:用于修改已存在表中的记录的内容。
flyway需要的权限
mysql5.7
GRANT SELECT ON `performance_schema`.user_variables_by_thread TO ${user}@'%' identified by '${password}';
flush privileges;
mysql 8
GRANT Select ON `performance_schema`.user_variables_by_thread TO ${user}@`%`;
flush privileges;
- ${user}为用户名。
-
${password}为密码。
user_variables_by_thread(用户自定义变量记录表)
performance_schema提供了一个保存用户定义变量的user_variables_by_thread表(该表也保存由mysql内部链接线程建立的变量)。这些变量是在特定会话中定义的变量,变量名由@字符开头。
咱们先来看看表中记录的统计信息是什么样子的。
admin@localhost : performance_schema 01:50:16> select * from user_variables_by_thread;
+-----------+-------------------------+--------------------------------------+
| THREAD_ID | VARIABLE_NAME | VARIABLE_VALUE |
+-----------+-------------------------+--------------------------------------+
| 45 | slave_uuid | 4b0027eb-6223-11e7-94ad-525400950aac |
| 45 | master_heartbeat_period | 5000000000 |
| 45 | master_binlog_checksum | CRC32 |
+-----------+-------------------------+--------------------------------------+
3 rows in set (0.01 sec)
表中各字段含义以下:
-
THREAD_ID:定义变量的会话的线程标识符(ID)
-
VARIABLE_NAME:定义的变量名称,在该表中去掉了@字符的形式显式
-
VARIABLE_VALUE:定义的变量值
user_variables_by_thread表不容许使用TRUNCATE TABLE语句。
版本交叉上线
如:1.5.0线上先线,1.4.0后上线。
1.4.0上线时,需要设置spring.flyway.out-of-order=true,1.4.0上线时,就不会报错了。
1.4.0上线后,当前版本依然会是1.5.0。
Current version of schema `flyway`: 1.5.0
还原spring.flyway.out-of-order=false后,版本要高于1.5.0才行。
修改版本SQL
UPDATE flyway_schema_history
SET version = REPLACE ( version, '1.0.0', '2.0.0' ),
script = REPLACE ( script, '1.0.0', '2.0.0' )
WHERE
installed_rank = 38;
- 1.0.0为被修改版本号
- 2.0.0为修改版本号。
git与flyway的结合使用
当git开发为1.0.0时,数据库里的flyway版本为2.0.0时,启动是没有问题的。
不过如果1.0.0,继续添加sql,就需要将out-of-order=true。
8、低版本flyway
报错:Detected resolved migration not applied to database on flyway
解决方案:配置下面的属性即可。
spring.flyway.ignore-missing-migrations=true #忽略缺失的升级脚本验证
场景
背景
- 多团队,多版本开发。
- 数据库没有隔离:开发环境。
- 数据库有隔离:测试环境,预生产环境,生产环境。
版本
团队A开发版本1.1,团队B开发版本1.2。
报错
只要团队A部署过测试环境,团队B就会报错。
原因:
- B团队代码中没有1.1版本,只有1.2版本。
- 1.1版本低于1.2版本。
解决办法
开发环境进行如下配置
spring.flyway.ignore-missing-migrations=true #忽略缺失的升级脚本验证
总结
- 数据库隔离:测试环境不同版本部署不同环境,就不会发生这种错误。
- 部署环境只有一套:预生产、生产环境只有一套环境,入口只有一个,也不会发生这种错误。