Flyway介绍
为什么要用Flyway
在项目团队开发模式下,代码可以通过SVN或者Git来对代码做版本控制,主要的目的就是为了解决多人开发代码冲突和版本回退的问题。
其实,数据库的变更也需要版本控制,在日常开发中,我们经常会遇到下面的问题: 【1】自己写的SQL忘了在所有环境执行,项目中存在dev/test/prod环境时可能就中间某个阶段出现遗漏。 【2】部分人直接在数据库上执行,部分提交脚本给测试执行,流程方法各种各样,缺少标准。 【3】别人写的SQL我们不能确定是否都在所有环境执行过了;一次系统重新部署,如果有人忘记提交脚本那么就可能导致运行中出现异常。 【3】有人修改了已经执行过的SQL,期望再次执行; 【4】需要新增环境做数据迁移; 【5】每次发版需要手动控制先发DB版本,再发布应用版本; 【6】缺乏自动化,标准化的操作流程 【7】很多项目团队都有SQL管理但是缺少一个简单有效的工具辅助。
参考
不同的开发人员在开发产品特性时,都有可能更新数据库(添加新表,新的约束等)。当开发人员完成工作并提交代码时,代码会被合并到主分支并在测试服务器上执行单元测试与集成测试。我们在哪个环节来执行数据库的更新操作呢?由QA 部门手工执行sql 脚本?或者我们开发一断程序自动执行数据库更新?以什么顺序来执行这些更新脚本?这些问题同样存在于生产环境。 我们的产品部署在不同的客户服务器上,以及很多的测试、联调、实验局、销售环境上。不同的客户和测试环境上都部署着不同版本的产品。当他们需要升级他们的产品到新的版本时,我们不仅需要让他们的管理员可以升级产品到新的版本,同时需要保留他们的已有数据。在升级产品的步骤中,我们清楚地知道客户数据库的当前版本,以及需要在该数据库上执行哪些数据库更新脚本,来更新数据库表结构与数据库中已存在的数据。当升级完成时,数据库表结构及数据应当与升级后的产品版本保持一致。 当升级失败时(比如在升级过程中出现网络连接失败),我们应当支持对失败进行修复。
一个项目单个环境迭代开发的过程中,对于数据库表的修改 DDL,可以通过版本控制工具一起进行控制。只需要在项目上线之前,人工执行新增的 DDL 即可,DDL 的版本是与当前项目迭代版本一致,细致点不至于出现问题。
单个环境版本迭代,数据库的版本号变更流程如下图: 一般而言,一个项目会同时部署到多套环境当中。随着项目迭代进行,不同环境的项目版本可能并非是同步一致的,甚至因为有的环境需要定制化开发,出现同一个项目多个分支,代码也愈行愈远。
多个环境版本迭代,数据库的版本号变更流程如下图: 于是在这种情况下,上线服务之前就很痛苦,要想起上线环境的当前表版本是多少,想不起来,就要对比线上库里的表,判断是否执行过了增量的 DDL,每个环境的增量 DDL 都可能是不同的,需要针对每个环境写不同的 DDL,发布时战战兢兢地生怕漏了执行哪个版本的 DDL 导致线上 Bug。
Flyway主要是用来记录每次迭代的版本sql的表结构的变动.在部署某个版本时,能够快找到这个版本对应的sql,避免出现表结构的错误。
Flyway原理简单阐述:开发者将每个版本的 DDL 放到项目中,项目在新环境启动时,会自动创建一张表用于记录 DDL 的版本信息,随后自动执行未执行过的 DDL,同时将执行过的 DDL 信息存入元数据表中。下次再启动时,检测到执行过了,就不会重复执行。
Flyway 大受欢迎是因为它具有简单非常容易安装和学习,同时迁移的方式也很容易被开发者接受。 Flyway 专注于搞数据库迁移、版本控制而并没有其它副作用。专为连续交付而设计。让Flyway在应用程序启动时迁移数据库。
Flyway 的特性
【1】自动升级(自动发现更新项):Flyway 会将任意版本的数据库升级到最新版本。Flyway 可以脱离JVM 环境通过命令行执行,可以通过Ant 脚本执行,通过Maven 脚本执行(这样就可以在集成环境自动执行),并且可以在应用中执行(比如在应用启动时执行)。 【2】规约优于配置:Flyway 有一套默认的规约,所以不需要修改任何配置就可以正常使用。 【3】既支持SQL 脚本,又支持Java 代码:可以使用SQL 脚本执行数据库更新,也可以使用Java 代码来进行一些高级数据升级操作。 【4】高可靠性:在集群环境下进行数据库升级是安全可靠的。 【5】支持清除已存在的库表结构:Flyway 可以清除已存在的库表结构,可以从零开始搭建您的库表结构,并管理您的数据库版本升级工作。 Tips:这个可要慎重,如无特别需求请禁用吧 【6】支持失败修复。新的2.0 版本提供了repair 功能,用于解决数据库更新操作失败问题。
认识
flyway是一个能对数据库变更做版本控制的工具。 GitLab地址:https://github.com/flyway/flyway
flyway提供了以下方式来使用 【1】Command-line 【2】Java API 【3】Maven 【4】Gradle
这里主要说明SpringBoot集成flyway,个人觉得这种集成在项目中更容易容易管理和配置。 引入如下依赖,SpringBoot项目不需要指定版本,已经帮我们做了集成。
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
添加如下配置信息
spring:
flyway:
enabled: true
clean-disabled: true
locations: classpath:db/migration,classpath:db/callback
table: flyway_schema_history
baseline-on-migrate: true
baseline-version: 0
encoding: UTF-8
out-of-order: false
schemas: flyway
validate-on-migrate: true
然后,在src/main/resources目录下面新建db.migration文件夹,默认情况下,该目录下的.sql文件就算是需要被flyway做版本控制的数据库SQL语句。
测试:在db.migration目录下新建1.0.1目录,并在该目录下创建V1__create_user.sql脚本文件
CREATE TABLE IF NOT EXISTS `T_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,
`UPDATED_TIME` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`USER_ID`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
启动项目,可以看到下面输出的日志,因为使用的是默认的数据库所以新建了flyway数据库,项目中可以指定已存在的数据库,然后并创建了T_USER数据表。
实践整理
修改一个已经执行过的V开头的SQL情况
在日常工作开发中,可能会出现无意间修改了原来已经执行的SQL文件,然后提交到了生产环境,那么会不会再次被执行,如果被执行了就会对线上造成影响。 上面截图中2.2.1/V1__create_user.sql和2.2.2/V2__add_user.sql已经被执行了而且希望只被执行一次,那么现在修改其中的SQL文件(不修改文件的名称) 其中红色的部分表示的变更的部分,重启项目程序 重启项目时抛出异常,大致的意思就是检查校验和时无法匹配,即V开头的SQL文件不允许被修改。Applied to database : -1328025572是文件之前的checksum, Resolved locally : -2095201518是变更后计算得到的校验和,不匹配无法通过验证。但是以R开头的是可以重复执行的。 【注意】以上可以看到Flyway通过一种约定的方式对SQL文件进行了一种规则处理,这能够很好的避免重复执行,这也告诉了我们一定要保证flyway_schema_histor数据表的完整性。
测试一个会出现异常的SQL情况
测试一个语法有问题的SQL语句项目启动时检查报错 不出意外的抛出了异常,但是需要注意的是,虽然项目启动抛出了异常,但是在flyway_schema_history表中却成功插入了一条记录,这条记录的success标识位0,标识失败。 当我尝试修正SQL并重新执行时,又发生了一个新的问题 FlywayException: Detected failed migration to version:3 根据描述问题肯定发生在2.2.4/V3__student_ddl.sql记录上,最终经过排查定位到了问题所在: 在flyway_schema_history的2.2.4/V3__student_ddl.sql记录发生了一次失败的数据库脚本变更,你必须去解决这个问题,而这个解决不能通过修改SQL重新执行,必须通过flyway的repair 命令去修正该条记录。官方的方式是flyway repair命令去修复,但是这种方式感觉并不适用于实际开发,我总不能通过命令行的方式或者maven插件的方式去链接多个环境的数据库吧,这样的操作不太合适,最简单直接的方式就是删除这一条记录,然后重新启动项目执行。
参考解决方式
【1】手动删除激励
直接删除flyway_schema_history表中迁移失败的记录,然后重新迁移
【2】flyway repair工具命令行
安装Flyway插件,并配置数据库信息,通过命令行:flyway repair 去处理
【3】mvn flyway:repair
在项目中添加maven插件,然后执行mvn flyway:repair。这个需要单独配置数据库连接地址。
【4】自定义实现机制
自定义实现机制,可以实现FlywayMigrationStrategy接口,自定义处理逻辑,这个并没有去深入了解,参考:https://docs.spring.io/spring-boot/docs/1.4.0.RC1/reference/htmlsingle/#howto-execute-flyway-database-migrations-on-startup
【5】设置Flyway Callbacks(推荐)
以上几种方式都是手动处理的,如果想要自动清理失败的迁移记录,可以使用afterMigrateError Flyway callback. 1:在spring配置文件中添加如下映射配置 spring.flyway.locations=classpath:db/migration,classpath:db/callback 2:在resources创建db/callback目录,并添加afterMigrateError__repair.sql脚本文件 3:为了看到效果,先在该脚本下添加如下测试SQL update flyway_schema_history set success=99 where version=4 4:在db/migration目录下创建V4__student_ddl.sql脚本,并故意设置一个语法错误 5:启动项目,此时项目启动失败,flyway_schema_history插入一条记录,并且success标识被置为99 6:基于上面的实例,我们就可以在该脚本中添加DELETE FROM flyway_schema_history WHERE success=false; 脚本语句,这表示当发生错误迁移记录时在回调时删除错误迁移记录,以便后面可以修改后重新启动,不需要在手动执行。 7:这里更推荐这一种方式,首先这一种方式是自动化的操作不需要手动修复,其次当发生错误时项目还是启动失败,不存在说问题被隐藏了,而且对于那一条错误数据来说,后续不管是通过手动脚本还是命令行的方式都是删除,最终的结果都是一样的。 8:这里的回调机制不仅仅可以实现这个需求,在其他一些方面也是可以应用的。
文件命名执行规则
Flyway所执行的SQL语句命名需要遵从一定的规范,否则运行的时候flyway会报错。下图是官方给出的命名规范: 【Prefix】V表示版本标识,以V开头的文件仅会被执行一次,后面跟上"0~9"数字的组合,数字之间可以用“.”或者下划线"_"分割开,然后再以两个下划线分割,其后跟文件名称,最后以.sql结尾。比如,V1.1.1__create_user_ddl.sql、V1.1_2__add_user_dml.sql。U开头的文件标识撤销回滚文件,很尴尬,社区版本貌似不支持这个功能。R开头的文件标识可重复运行的SQL,后面再以两个下划线分割,其后跟文件名称,最后以.sql结尾。。比如,R__truncate_user_dml.sql。可重复执行不是每次在启动的时候都会执行,而是会校验SQL文件是否发生了变更,如果未发生变更是不会执行的。V开头的SQL执行优先级要比R开头的SQL优先级高。 【Version】版本号,对应flyway_schema_history表的version字段。 【Separator】两个下划线,两个下划线,两个下划线,强调下。 【Description】文件描述,描述当前文件实际功能和作用。对应flyway_schema_history表的description字段。 【Suffix】.sql文件
目录的命名不会影响flyway对SQL的识别和运行,可以自行取名和分类。
Flyway 是如何比较两个 SQL 文件的先后顺序呢?它采用 采用左对齐原则, 缺位用 0 代替 。举几个例子: 1.0.1.1 比 1.0.1 版本高。 1.0.10 比 1.0.9.4 版本高。 1.0.10 和 1.0.010 版本号一样高, 每个版本号部分的前导 0 会被忽略。
相关命令
migrate:数据库迁移命令,会根据设置好的SQL脚本直接将数据库表升级至最新版本。 clean:删除数据库中所有的表,千万别在生产环境上使用。 info:打印所有关于数据库迁移的详细信息和状态信息。 validate:验证数据库迁移是否可用。 undo:对数据库迁移进行回滚操作。 baseline:以现有数据库为基准,创建flyway_schema_history 表,大于基准版本的数据库迁移才会被应用。 repair:修复flyway_schema_history表。
JPA Buddy自动生成差异SQL
JPA Buddy一般用作自动生成 JPA 实体类,但是它的功能不仅仅如此,它还集成了Flyway,可以帮助我们自动生成版本变更SQL语句。在IDEA上安装 JPA Buddy插件,安装完成之后还需要添加下jpa的依赖才会展示控制面板,这里只是引入该依赖,不是代表需要使用JPA。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
控制面板如下 勾选+符号 如上图所示,其中Empty Versioned Migration、Diff Versioned Migration、Init Schema Migration、SQL Call Back都是和Flyway关联的功能。本次演示下Diff Versioned Migration这也是我最感兴趣的功能,即对比两个数据库的版本差异,比如DEV和TEST的差异,然后自动生成差异SQL,然后直接提交给测试项目即可。 在上面配置两个环境的数据库,然后选中Diff Versioned Migration,这里需要等待一段时间,数据表较多,如下图所示,该工具自动帮助我们生成了DML和DDL语句,并且自动将这些代码合并在一个文件,还直接放到了db/migration目录下,非常的方便,这样我们后续直接运行项目即可
问题整理
Detected resolved migration not applied to database
这个问题是因为Flyway所识别到的当前版本记录和数据库保存的最新版本记录次序颠倒、发生冲突又或者存在失败迁移记录,解决方式参考 1:修改当前sql文件名称 2:删除或修改数据库中的最大的版本记录 3:flyway Repair
参考案例:本篇博文(2)测试一个会出现异常的SQL情况模块
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
小结
【1】SQL语句命名需要遵从一定的规范,以V或者R开头,然后是双下划线…; 【2】尽量不要修改已经执行过的SQL,即便是R开头的可反复执行的SQL,它们会不利于数据迁移; 【3】生产务必禁 spring.flyway.cleanDisabled=false 【4】spring.flyway.outOfOrder配置,个人使用中看到很多处理flyway迁移失败异常时使用该方式,但是这里还是不建议放开该配置,严格管理还是有必要的,保持out-of-order: false不允许不按顺序迁移。 【5】撤消迁移(例如删除先前迁移中添加的列)可能会非常棘手,并且通常需要开发人员创建数据备份。 【6】项目中也可以根据需要创建一个初始版本,这个初始版本可以将现有数据库通过某些工具导出SQL脚本放上去,作为一个基础版本。 【7】spring-boot也集成很多flyway的功能,包括自定义策略配置、回调接口实现等等,可以通过这个进行适当的扩展。 【8】总的来看,通过flyway来管理和执行SQL,在一定程度上保证SQL脚本的版本化和规范化,同时通过该工具也进一步提供了更加自动化的处理方式。
参考
Capital One 使用Flyway进行数据库迁移
https://jiagoushi.pro/capital-one-database-migrations-flyway
在大型项目中使用Flyway进行数据库迁移 https://jiagoushi.pro/database-migration-flyway-large-project
Database Migrations with Flyway https://www.baeldung.com/database-migrations-with-flyway#generate-versioned-migrations-in-intellij-idea
官网:https://flywaydb.org/documentation/
|