想到要管理数据库的版本,是在实际产品中遇到问题后想到的一种解决方案,当时各个环境的数据库乱作一团,没有任何一个人(开发、测试、维护人员)能够讲清楚当前环境下的数据库是哪个版本,与哪个版本的应用相匹配,如何升级到与新版本的应用相匹配。想到管理数据库版本时,先是心底形成了一个初步的解决方案,大致是通过数据库中的某张表来记录数据库表结构的历次更新与对应版本,在每次数据库表结构调整时除了提供库表更新sql ,还必须提供更新记录与对应版本的sql,以帮助维护数据库版本信息,并在遇到问题时提供相关的排查依据。后来据此思路在网络上搜索了一把,结果搜到Liquibase (另一款开源数据库版本管理工具)。在学习了解Liquibase 的时候,经高手介绍又了解到了Flyway 这个项目的存在。经过一番了解,最后我们选择了Flyway ,主要原因是Flyway 支持sql 脚本,而Liquibase 只支持XML 方式的数据库表结构定义,虽然在新的版本中号称在XML 的数据库表结构定义方式中支持了sql 脚本。虽然Flyway 的中文文档近乎为零,英文文档也凤毛麟角,但它却是我们最理想的数据库版本管理工具,它不但支持sql 脚本,还支持Java 代码直接操作数据库(在版本升级时做数据迁移相当有用),有Maven 插件,支持命令行(我们的平台数据库有部分由C 语言项目管理),而且在spring 框架的配合下,很容易就能实现应用启动时自动检查并升级数据库的功能。
数据库开发遇到的问题:- 不同的开发人员在开发产品特性时,都有可能更新数据库(添加新表,新的约束等)。当开发人员完成工作并提交代码时,代码会被合并到主分支并在测试服务器上执行单元测试与集成测试。我们在哪个环节来执行数据库的更新操作呢?由QA 部门手工执行sql 脚本?或者我们开发一断程序自动执行数据库更新?以什么顺序来执行这些更新脚本?这些问题同样存在于生产环境。
- 我们的产品部署在不同的客户服务器上,以及很多的测试、联调、实验局、销售环境上。不同的客户和测试环境上都部署着不同版本的产品。当他们需要升级他们的产品到新的版本时,我们不仅需要让他们的管理员可以升级产品到新的版本,同时需要保留他们的已有数据。在升级产品的步骤中,我们清楚地知道客户数据库的当前版本,以及需要在该数据库上执行哪些数据库更新脚本,来更新数据库表结构与数据库中已存在的数据。当升级完成时,数据库表结构及数据应当与升级后的产品版本保持一致。
- 有的时候,我们需要通过代码(Java )来维护一些已存在的数据,如通过代码来维护blob 类型的用户头像数据。
- 当升级失败时(比如在升级过程中出现网络连接失败),我们应当支持对失败进行修复。
- 自动升级(自动发现更新项):Flyway 会将任意版本的数据库升级到最新版本。Flyway 可以脱离JVM 环境通过命令行执行,可以通过Ant 脚本执行,通过Maven 脚本执行(这样就可以在集成环境自动执行),并且可以在应用中执行(比如在应用启动时执行)。
- 规约优于配置:Flyway 有一套默认的规约,所以不需要修改任何配置就可以正常使用。
- 既支持SQL 脚本,又支持Java 代码:可以使用SQL 脚本执行数据库更新,也可以使用Java 代码来进行一些高级数据升级操作。
- 高可靠性:在集群环境下进行数据库升级是安全可靠的。
- 支持清除已存在的库表结构:Flyway 可以清除已存在的库表结构,可以从零开始搭建您的库表结构,并管理您的数据库版本升级工作。
- 支持失败修复。新的2.0 版本提供了repair 功能,用于解决数据库更新操作失败问题。
Flyway 是独立于数据库的应用、管理并跟踪数据库变更的数据库版本管理工具。Flyway 的项目主页是 http://flywaydb.org/ 。Flyway 和 Liquibase 都是 Java 项目中常用的 DB migration 工具, 从使用简便性看,Flyway 比 Liquibase 更简单, 从 github 的 star 数量看, flyway 更受欢迎。对于 SpringBoot 项目开发, 其实不需要专门安装 flyway 命令行工具和 maven 插件, SpringBoot 启动就会自动执行 DB migrate 操作. 对于其他的 flyway 操作, 就需要使用命令行工具或 maven 插件了。
- 版本:对数据库的每一次变更可称为一个版本。
- 迁移:Flyway把数据库结构从一个版本更新到另一个版本叫做迁移。
- 可用的迁移:Flyway的文件系统识别出来的迁移版本。
- 已经应用的迁移:Flyway已经对数据库执行过的迁移。
flyway 提供命令行工具, 常用的命令包括:
- Clean: 删除所有创建的数据库对象, 包括用户、表、视图等。(注意不要在生产库上执行 clean 操作)
- Migrate: 对数据库依次应用版本更改.
- Info: 获取目前数据库的状态. 那些迁移已经完成, 那些迁移待完成. 所有迁移的执行时间以及结果.
- Validate: 验证已 Apply 的脚本是否有变更, Flyway 的 Migration 默认先做 Validate.
- Baseline: 根据现有的数据库结构生成一个基准迁移脚本.
- Repair: 修复命令尽量不要使用, 修复场景有: 1. 移除失败的 migration 记录. 2.已经应用的 SQL 脚本被修改, 我们想重新应用该 SQL 脚本.
maven 插件, 最新 maven 插件见 https://mvnrepository.com/artifact/org.flywaydb/flyway-maven-plugin。maven插件命令, mvn flyway:migrate
org.flywaydb
flyway-maven-plugin
4.0.3
注意:编写数据库的版本的脚本文件,放到src/main/resources的flyway里面:(flyway找脚本的时候默认去src/mian/resources下面的db/migration,如果要放在别的位置,后面的地方要配置一下)
四、Flyway 的工作原理flyway 需要在 DB 中先创建一个 metdata 表 (缺省表名为 flyway_schema_history), 在该表中保存着每次 migration 的记录, 记录包含 migration 脚本的版本号和 SQL 脚本的 checksum 值. 当一个新的 SQL 脚本被扫描到后, Flyway 解析该 SQL 脚本的版本号, 并和 metadata 表已 apply 的的 migration 对比, 如果该 SQL 脚本版本更新的话, 将在指定的 DB 上执行该 SQL 文件, 否则跳过该 SQL 文件。两个 flyway 版本号的比较, 采用左对齐原则, 缺位用 0 代替. 举例如下:
1.2.9.4 比 1.2.9 版本高. 1.2.10 比 1.2.9.4 版本高. 1.2.10 和 1.2.010 版本号一样高, 每个版本号部分的前导 0 会被忽略.
Flyway SQL 文件可以分为两类: Versioned 和 Repeatable. Versioned migration 用于版本升级, 每个版本有唯一的版本号并只能 apply 一次. Repeatable migration 是指可重复加载的 migration, 一旦 SQL 脚本的 checksum 有变动, flyway 就会重新应用该脚本. 它并不用于版本更新, 这类的 migration 总是在 versioned migration 执行之后才被执行.默认情况下, Migration SQL的命名规则如下图:
其中的文件名由以下部分组成,除了使用默认配置外,某些部分还可自定义规则.
- prefix: 可配置,前缀标识,默认值 V 表示 Versioned, R 表示 Repeatable
- version: 标识版本号, 由一个或多个数字构成, 数字之间的分隔符可用点.或下划线_
- separator: 可配置, 用于分隔版本标识与描述信息, 默认为两个下划线__
- description: 描述信息, 文字之间可以用下划线或空格分隔
- suffix: 可配置, 后续标识, 默认为.sql
flyway 的 metadata 表结果如下:
CREATE TABLE flyway_schema_history(
installed_rank INT NOT NULL,
version VARCHAR(50),
description VARCHAR(200) NOT NULL,
type VARCHAR(20) NOT NULL,
script VARCHAR(1000) NOT NULL,
checksum INT,
installed_by VARCHAR(100) NOT NULL,
installed_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
execution_time INT NOT NULL,
success TINYINT(1) NOT NULL,
PRIMARY KEY (installed_rank),
INDEX flyway_schema_history_s_idx (success)
)
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
pom.xml配置如下(spring-boot-starter-parent 包没有使用最新的 2.0.5, 最新版总是导致 HikariPool 无法初始化, 所以选择的版本是 2.0.4)
org.springframework.boot
spring-boot-starter-parent
2.0.4.RELEASE
flyway 其实仅依赖 spring-boot-starter-jdbc 包,
org.flywaydb
flyway-core
org.springframework.boot
spring-boot-starter-jdbc
mysql
mysql-connector-java
加上 spring-boot-maven-plugin , 可生成 fat jar.
org.springframework.boot
spring-boot-maven-plugin
application.properties 参数
## 设定 db source 属性
spring.datasource.url=jdbc:mysql://localhost:3306/world
spring.datasource.username=root
spring.datasource.password=toor
## 设定 flyway 属性
spring.flyway.cleanDisabled = true
# flyway 的 clean 命令会删除指定 schema 下的所有 table, 杀伤力太大了, 应该禁掉.
spring.flyway.enabled = true
# 启用或禁用 flyway
spring.flyway.locations =classpath:db/migration
# 设定 SQL 脚本的目录,多个路径使用逗号分隔, 比如取值为 classpath:db/migration,filesystem:/sql-migrations
spring.flyway.baselineOnMigrate=true
# 如果指定 schema 包含了其他表,但没有 flyway schema history 表的话, 在执行 flyway migrate 命令之前, 必须先执行 flyway baseline 命令.
# 设置 spring.flyway.baseline-on-migrate 为 true 后, flyway 将在需要 baseline 的时候, 自动执行一次 baseline.
spring.flyway.baselineVersion=1
# 指定 baseline 的版本号,缺省值为 1, 低于该版本号的 SQL 文件, migrate 的时候被忽略.
#spring.flyway.encoding=
# Encoding of SQL migrations (default: UTF-8)
spring.flyway.table=flyway_schema_history_myapp
# 设定 flyway 的 metadata 表名, 缺省为 flyway_schema_history
spring.flyway.outOfOrder=true
# 开发环境最好开启 outOfOrder, 生产环境关闭 outOfOrder .
#spring.flyway.schemas=
# 需要 flyway 管控的 schema list, 缺省的话, 使用的时 dbsource.connection直连上的那个 schema, 可以指定多个schema, 但仅会在第一个schema下建立 metadata 表, 也仅在第一个schema应用migration sql 脚本. 但flyway Clean 命令会依次在这些schema下都执行一遍.
更多参数见 https://flywaydb.org/documentation/configfiles,这些参数配到springboot2 项目中, 需要加上 spring. 前缀.
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.
五、Flyway 最佳实践
1. SQL 的文件名
开发环境和生产环境的 migration SQL 不共用. 开发过程往往是多人协作开发, DB migration 也相对比较频繁, 所以 SQL 脚本会很多. 而生产环境 DB migration 往往由 DBA 完成, 每次升级通常需要提交一个 SQL 脚本.
(1). 开发环境 SQL 文件建议采用时间戳作为版本号.
开发环境 SQL 文件建议采用时间戳作为版本号, 多人一起开发不会导致版本号争用, 同时再加上生产环境的版本号, 这样的话, 将来手工 merge 成生产环境 V1.2d migration 脚本也比较方便, SQL 文件示例: V20180317.10.59__V1.2_Unique_User_Names.sql V20180317.14.59__V1.2_Add_SomeTables.sql
(2). 生产环境 SQL 文件, 应该是手动 merge 开发环境的 SQL 脚本, 版本号按照正常的版本, 比如
V2.1.5_001__Unique_User_Names.sql
2. migration 后的SQL 脚本不应该再被修改. 3. spring.flyway.outOfOrder 取值 true /false对于开发环境, 可能是多人协作开发, 很可能先 apply 了自己本地的最新 SQL 代码, 然后发现其他同事早先时候提交的 SQL 代码还没有 apply, 所以 开发环境应该设置 spring.flyway.outOfOrder=true, 这样 flyway 将能加载漏掉的老版本 SQL 文件; 而生产环境应该设置 spring.flyway.outOfOrder=false
4. 多个系统公用要 DB schema很多时候多个系统公用一个 DB schema , 这时候使用 spring.flyway.table 为不同的系统设置不同的 metadata 表, 缺省为 flyway_schema_history