Flyway详解(使用说明及避坑指南、一文搞懂flyway)
一、简介
1.1 Flyway是什么?
Flyway是一款开源的数据库版本管理工具,可以实现管理并跟踪数据库变更,支持数据库版本自动升级,而且不需要复杂的配置,能够帮助团队更加方便、合理的管理数据库变更。
例:创建两个sql变更文件,项目启动后会将两个文件中的sql语句全部执行。
1.2 为什么使用Flyway?
简单举个例子:开发时,如果A开发和B开发都对同一数据库进行了修改,那么如何进行数据同步呢?假如多个开发人员都修改了sql脚本,怎么同步到测试环境和生产环境?
类似于以上的情况在日常开发中不胜枚举,在最开始的单体架构中,我们公司采用了通过校验数据库版本号来实现sql的变更,这虽然能够解决大部分问题,但每次都需要维护变更sql的代码和数据库版本号,且对于后续服务上云也是十分不便的。因此我们需要对数据库校验的方案进行调整,就去调研了目前市面上比较流行的Flyway和Liquibase,综合对比下来,最终选择了Flyway作为后续的数据库版本管理工具,具体差异如下:
1.3 Flyway的工作原理
Flyway在第一次执行时,会创建一个默认名为flyway_schema_history的历史记录表,这张表会用来跟踪或记录数据库的状态,然后每次项目启动时都会自动扫描在resources/db/migration下的文件的版本号并且通过查询flyway_schema_history来判断是否有新增文件,从而判断是否进行迁移。
默认的查找 migration 的路径为 classpath:db/migration ,对应 SQL 文件可放置在src/main/resources/db/migration 下,Java 类可放置在 src/main/java/db/migration 下。
Flyway在第一次执行时,会创建一个默认名为flyway_schema_history的历史记录表,这张表会用来跟
踪或记录数据库的状态,然后每次启动时都会自动扫描在resources/db/migration下的文件并且通过查
询flyway_schema_history来判断是否为新增文件,从而判断是否进行迁移。
默认的查找 migration 的路径为 classpath:db/migration ,对应 SQL 文件可放置在
src/main/resources/db/migration 下,Java 类可放置在 src/main/java/db/migration 下
1.3.1 Flyway的校验版本号算法
flyway在升级数据库时会先计算之前已经升级过的脚本的checksum值和数据库的checkSum值进行比对,如果老脚本发生了变化后checkSum校验就会失败,从而抛出异常,checkSum计算算法为(CRC32 (循环冗余校验码)算法)。
新增的脚本则会和数据库中的版本号进行比较,如果小于数据库存储的最后一个版本号,也不会继续执行。
1.3.2 Flyway的锁机制
Flyway使用数据库锁机制(locking technology of your database)来协调多个节点,从而保证多套应用程序可同时执migration,而且集群控制也可做配置。基于数据库锁机制实现分布式锁有两种,基于数据库表和基于数据库排他锁,Flyway采用的是基于数据库排他锁。
排他锁(Exclusive Locks,简称X锁),又称为写锁、独占锁,在数据库管理上,是锁的基本类型之一。若
事务T对数据对象A加上X锁,则只允许T读取和修改A,其他任何事务都不能再对A加任何类型的锁,直到T释放
A上的锁。这就保证了其他事务在T释放A上的锁之前不能再读取和修改A
源码如下:
@Override
public <T> T lock(Table table, Callable<T> callable) {
if (database.isPxcStrict()) {
return super.lock(table, callable);
}
return new MySQLNamedLockTemplate(jdbcTemplate,table.toString().hashCode()).execute(callable);
}
@Override
protected void doLock() throws SQLException {
jdbcTemplate.execute("SELECT * FROM " + this + " FOR UPDATE");
}
private boolean tryLock() throws SQLException {
return jdbcTemplate.queryForInt("SELECT GET_LOCK(?,10)", lockName) == 1;
}
1.3.3 Flyway连接数据库
flyway使用JDBC连接数据库
private boolean hasUserVariableResetCapability() {
try {
jdbcTemplate.queryForStringList(userVariablesQuery);
return true;
} catch (SQLException e) {
LOG.debug("Disabled user variable reset as " + (database.isMariaDB() ? USER_VARIABLES_TABLE_MARIADB :USER_VARIABLES_TABLE_MYSQL)+ "cannot be queried (SQL State: " + e.getSQLState() + ",Error Code: " + e.getErrorCode() + ")");
return false;
}
}
1.3.4 Flyway的启动速度
耗时主要来源两个方面
(1).Flyway依次读取脚本中内容时的IO开销
(2).Flyway计算脚本checksum值的算法开销
对于IO开销而言,每个脚本如果不是涉及大量的数据变更,只是表结构的变更,脚本的大小都非常小,可以不考虑。
二、Flyway的使用
接下来就进入正题了,flyway该如何使用呢?
以下是Java的使用方式。
2.1 添加依赖
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>6.5.7</version>
</dependency>
2.2 添加配置
java
spring:
flyway:
#是否启用
enabled: true
# 可以支持多个location, 用','隔开
locations: classpath:db/migration
#是否创建元数据表
validate-on-migrate: true
# flyway 的 clean 命令会删除指定 schema 下的所有 table, 生产务必禁掉。这个默认值是
false 理论上作为默认配置是不科学的。
clean-disabled: true
# 如果数据库不是空表,需要设置成 true,否则启动报错
baseline-on-migrate: true
# 版本控制日志表,默认flyway_schema_history,不同系统建议修改数据
# table: flyway_schema_history
2.3 创建sql文件
2.4 启动项目即可
三、Flyway详解
3.1 命令行
1.常用命令Migrate, Clean, Info, Validate, Baseline, Repair。
2.Migrate是指把数据库默认数据库迁移到最新版本。
3.Clean这个命令会清除指定库下所有的对象,包括table、view、triggers…,让数据库变成空的状态。
4.info用于打印所有Migrations的详细和状态信息。
5.Validate是指验证已经应用的Migrations是否有变更,Flyway是默认是开启验证的。
6.Baseline针对已经存在表结构的数据库的一种解决方案,即实现在非空数据库中新建Metadata表,并把Migrations应用到该数据库。
7.Repair操作能够修复Metadata表,该操作在Metadata表出现错误时是非常有用的。
8.Repair会修复Metadata表的错误,通常有两种用途:
①移除失败的Migration记录,该问题只是针对不支持DDL事务的数据库。
②重新调整已经应用的Migratons的Checksums值,比如:某个Migratinon已经被应用,但本地进行了修改,又期望重新应用并调整Checksum值,不过尽量不要这样操作,否则可能造成其它环境失败。
3.2 配置行
flyway配置详解
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.
3.3 执行方式
Flyway 的 migration 会在 Spring Boot 应用启动时自动执行,如果不想通过启动应用的方式执行,官方
提供了命令行、API、以及 Maven 和 Gradle 插件的方式,但总的来说都会麻烦一些,因为需要将已经
在 Spring Boot 中配置的参数,再到其他执行方式所各自要求的位置重新配置一遍,实用性一般。
3.4 sql脚本命名规则
1.仅需要执行一次的,以大写“V”开头,V+版本后(版本号间的数字以“.” 或者“ _ ”分隔开,“ _ ”会自动编译
成 “ . ” )+" __"+文件描述+后缀名
2.需要执行多次的,以大写“R”开头,命名如R__clean.sql ,R的脚本只要改变了就会执行,R不带版本号
3.V开头的比R开头的优先级要高。
前缀:用于版本控制(可配置)、撤消(可配置)和可重复迁移(可配置)VUR)
版本:带有点或下划线的版本可根据需要分隔任意数量的部分(不适用于可重复的迁移)
分隔符:(两个下划线)(可配置)__)
说明:下划线或空格分隔单词
后缀:(可配置.sql)
(可选)版本控制 SQL 迁移还可以省略分隔符和说明
四 开发时注意事项
1.报错后需要删除flyway_schema_history中记录,否则启动失败
2.V的优先级高于R,假如三个V迁移脚本和一个R(无论新建还是修改)一起执行,其中一个V报错,则V会
全部执行完成且记录到flyway_schema_history中,而R不执行且不记录,删除表中报错记录后,查询启
动,则执行原错误V和未执行的R
3.多个要执行的R中,如果出现了其中一个出现了错误,则在其后的R都不执行
4.R的执行顺序根据命名来进行排序
5.一个文件中ddl并不由一个事务管理,比如创建三个表,中间创建表语句报错,则第一个表还是会创建
成功并且提交事务
6.已经执行过的迁移文件(V)不能修改,否则报错。
7.同一个迁移文件下同表内DDL无法回滚,DML可回滚, 从报错点开始不往下执行,Flyway使用数据库锁
机制
8.版本号相同会报错(Found more than one migration with version 1.0.0.9)
9.同一个迁移文件下假设都是dml,那么如果中间出现错误,所有的dml语句都会回滚
10.删除sql文件后启动会报错,报错如下:
If you removed this migration intentionally, run repair to mark the migration as
deleted.
五、部署上线时注意事项
- 如果不手动创建元数据表,则需要进行以下配置,用于自动创建
validate-on-migrate: true
If you removed this migration intentionally, run repair to mark the migration as
deleted. - 如果数据库不是空表,则需进行以下配置,否则启动报错
baseline-on-migrate: true - clean命令会删除数据库中所有表,包括数据,结构等,这是不合理的,所以需要进行以下配置
clean-disabled: true (该配置由于默认值不合理,所以在V9版本中修改默认值由false为true) - 使用flyway要注意版本兼容问题,springboot与flyway,flyway与数据库版本,否则启动报错
- 如果启动的时候像忽略某些迁移文件,可进行以下参数配置
baseline-version=20210809,以忽略 20210809 版本以及之前的所有 migration - 多人开发中,如果一个人提交V2一个人提交V1,而V2先入库执行了,那么V1入库就不会执行,如
果需要执行则需进行如下配置,但是不建议这么做
out-of-order=true
六、 支持的数据库版本
目前支持mysql5.7的社区版为7.15.0,支持mysql8.0的版本是8.2.0,8.2.1移除了mysql支持,如文档原
文:
Extract MySQL code to plugin. This will need to be added as a new dependency.
flyway的8.2.1版本移除mysql的解决方案,增加依赖:
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
</dependency>
七、总结
Flyway的引用确实方便了后续我们团队的数据库版本管理,但各个团队使用场景皆有不同,所以本文只供想要学习Flyway的朋友借鉴,另:flyway的社区版同专业版相比也是有一些区别的,类似下面的两个方面,所以在选择Flyway时需考虑引入对整体架构的提升,本文分享到这里结束,感谢大家耐心观看。
①社区版目前不支持版本回退
②社区版本没有任务队列和异步任务的支持,所以在大量变更时略有风险,尽量拆分