摘要
在频繁发布版本的情况下,数据库版本难以控制一直是一个头疼的问题,本文主要介绍如何利用Flyway工具实现数据库版本控制。
概述
数据库的版本控制一般都是通过代码管理工具统一管理SQL脚本,但是仅仅是将脚本与代码一起管理,在应用升级时仍然会碰到很多问题:
-
无法确认在某台机器上的数据库版本
-
无法确认是否遗漏了某个数据库脚本未执行
-
无法确认在某台机器上执行过哪些数据库脚本
-
代码版本库无法管理数据库脚本依赖关系
所以,我们需要借助专业的数据库版本管理工具实现数据库的版本控制。Flyway就是一个数据库版本控制工具。Flyway主要有以下功能:
-
Flyway可以自动检测指定目录下的数据升级文件并升级至指定版本
-
Flyway可以自动检测指定目录下的数据回滚文件并回滚至指定版本(专业版功能)
-
Flyway可以检测已执行过的数据升级文件是否有改动及是否有错误
-
Flyway可随时展示当前数据库版本和已执行过的数据库升级
-
Flyway可以依照数据库脚本文件命名规则依次执行数据库脚本
Flayway简介
工作原理
- Flyway通过维护flyway_schema_history表记录数据库版本,升级数据库版本时Flyway将试图查询该表的数据,如果数据库是空的,Flyway将创建flyway_schema_history表。
- Flyway会按照版本号和执行顺序排序升级历史,执行升级脚本时也会按照版本号从低到高的顺序执行,下图展示了数据库从新建到升级至版本2的过程。
图2对应的数据库版本历史如下
installed_rank | version | description | type | script | checksum | installed_by | installed_on | execution_time | success |
---|---|---|---|---|---|---|---|---|---|
1 | 1 | Setup | SQL | V1__Initial_Setup.sql | 1996767037 | axel | 2016-02-04 22:23:00.0 | 546 | true |
2 | 2 | First Changes | SQL | V2__First_Changes.sql | 1279644856 | axel | 2016-02-06 09:18:00.0 | 127 | true |
- 当使用Flyway继续升级数据库时,Flyway将再次扫描文件系统或应用程序的类路径并与flyway_schema_history表中的内容比对,版本号小于或等于当前版本的升级文件将被忽略,其余的升级文件的状态将被标记为pending表示该升级文件可用但尚未执行,如图3执行升级后将新增一条记录到flyway_schema_history表。
图3对应的数据库版本历史如下,可以看到新增了一条记录:
installed_rank | version | description | type | script | checksum | installed_by | installed_on | execution_time | success |
---|---|---|---|---|---|---|---|---|---|
1 | 1 | Setup | SQL | V1__Initial_Setup.sql | 1996767037 | axel | 2016-02-04 22:23:00.0 | 546 | true |
2 | 2 | First Changes | SQL | V2__First_Changes.sql | 1279644856 | axel | 2016-02-06 09:18:00.0 | 127 | true |
3 | 2.1 | Refactoring | JDBC | V2_1__Refactoring | axel | 2016-02-10 17:45:05.4 | 251 | true |
利用Flyway命令行工具实现版本控制
本章将简单介绍利用Flyway实现数据库版本控制的过程。本章用来演示的数据库为本机MySQL数据库,数据库实例名为test。
安装
Flyway的安装过程比较简单,只需从官网下载安装包并解压至服务器并解压Flyway-commandline将其添加至环境变量即可。Flyway 社区版安装包目录结构如下:
配置
执行Flyway命令通常需要准备两种文件:Flyway配置文件和数据库升级脚本。数据库升级脚本即为普通SQL脚本,本文不做赘述。如图4所示,全局的Flyway配置文件位于Flyway安装包conf目录下。本文使用的配置文件如下:
1 2 3 4 5 | flyway.url=jdbc:mysql://localhost:3306/test #数据库jdbc连接地址 flyway.user=root #数据库用户名 flyway.password=password #数据库密码 flyway.schemas=test #需要升级的数据库实例名 flyway.encoding=UTF-8 #编码格式 |
注意:
- Flyway遵循约定优于配置原则,即在执行Flyway命令时可指定在配置文件中的参数,其优先级高于配置文件;
- 数据库密码可以执行命令时输入;
- Drivers目录下的jdbc驱动要与操作的数据库对应。
升级数据库
- 准备数据库升级脚本:按照flyway命名规范前缀+版本号+分隔符+描述+后缀(例如:V1_1_1_1__test_version.sql即表示该文件的版本号为1.1.1.1)命名各个升级脚本,将升级脚本放在flyway安装目录下的sql目录,执行flyway命令时flyway将自动扫描安装目录下的sql目录中的数据库升级文件。
- 使用info命令查看当前数据库状态,由于当前未做任何操作,输出内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 | G:\flywaydb\demo>flyway info Flyway Trial Edition 5.0.7 by Boxfuse Database: jdbc:mysql://localhost:3306/test (MySQL 5.5) Schema version: << Empty Schema >> +-----------+---------+--------------+------+--------------+---------+----------+ | Category | Version | Description | Type | Installed On | State | Undoable | +-----------+---------+--------------+------+--------------+---------+----------+ | Versioned | 1.0.0.0 | init sample | SQL | | Pending | No | | Versioned | 1.1.0.0 | feature 8126 | SQL | | Pending | Yes | | Versioned | 1.1.0.1 | bug 8527 | SQL | | Pending | No | +-----------+---------+--------------+------+--------------+---------+----------+ |
1 | 可以看到当前目录下共有3个数据库升级文件,其状态都为Pending,即表示Flyway已检测到该数据升级文件,但尚未执行。 |
- 使用migrate命令执行数据库升级,输出内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 | G:\flywaydb\demo>flyway migrate Flyway Trial Edition 5.0.7 by Boxfuse WARNING: You are using the 30 day limited Flyway Trial Edition. After 30 days y Database: jdbc:mysql://localhost:3306/test (MySQL 5.5) Successfully validated 4 migrations (execution time 00:00.045s) Creating Schema History table: `test`.`schema_history` Current version of schema `test`: << Empty Schema >> Migrating schema `test` to version 1.0.0.0 - init sample Migrating schema `test` to version 1.1.0.0 - feature 8126 Migrating schema `test` to version 1.1.0.1 - bug 8527 Successfully applied 3 migrations to schema `test` (execution time 00:07.092s) |
可以看到数据库已成功升级为最高版本1.1.0.1,且可以看到升级顺序为按照版本号从低到高依次升级。
- 使用info命令查看数据库状态,输出内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 | G:\flywaydb\demo>flyway info Flyway Trial Edition 5.0.7 by Boxfuse Database: jdbc:mysql://localhost:3306/test (MySQL 5.5) Schema version: 1.1.0.1 +-----------+---------+--------------+------+---------------------+---------+----------+ | Category | Version | Description | Type | Installed On | State | Undoable | +-----------+---------+--------------+------+---------------------+---------+----------+ | Versioned | 1.0.0.0 | init sample | SQL | 2018-05-11 10:50:12 | Success | No | | Versioned | 1.1.0.0 | feature 8126 | SQL | 2018-05-11 10:50:13 | Success | Yes | | Versioned | 1.1.0.1 | bug 8527 | SQL | 2018-05-11 10:50:13 | Success | No | +-----------+---------+--------------+------+---------------------+---------+----------+ |
可以看到数据库当前版本为1.1.0.1,共执行3次数据升级,状态均为成功。
管理已有数据库
上面的升级是基于一个没有数据的新库,在实际场景中都需要在已有数据的数据库上做版本管理。比如数据量比较大或已有应用在使用的数据库,不能清空数据库重新导入,Flyway通过baseline的概念也能很好的支持这种场景。下面我们就演示一下如何使用flyway管理已有数据库。我们仍使用与上面相同的配置文件和数据库升级文件,操作步骤如下:
- 使用clean命令清空数据库
1 2 3 4 | G:\flywaydb\demo>flyway clean Flyway Trial Edition 5.0.7 by Boxfuse Database: jdbc:mysql://localhost:3306/test (MySQL 5.5) Successfully cleaned schema `test` (execution time 00:01.705s) |
1 | 此时查询该数据库可以看到数据库已没有任何数据。 |
-
使用mysql客户端连接工具手动执行V1_0_0_0__init_sample.sql,现在数据库的版本为已初始化状态,即1.0.0.0版本,但是该版本号尚未被flyway管理。
-
使用flyway info查看当前数据库版本和状态:
1 2 3 4 5 6 7 8 9 10 11 12 | G:\flywaydb\demo>flyway info Flyway Trial Edition 5.0.7 by Boxfuse Database: jdbc:mysql://localhost:3306/test (MySQL 5.5) Schema version: << Empty Schema >> +-----------+---------+--------------+------+--------------+---------+----------+ | Category | Version | Description | Type | Installed On | State | Undoable | +-----------+---------+--------------+------+--------------+---------+----------+ | Versioned | 1.0.0.0 | init sample | SQL | | Pending | No | | Versioned | 1.1.0.0 | feature 8126 | SQL | | Pending | Yes | | Versioned | 1.1.0.1 | bug 8527 | SQL | | Pending | No | +-----------+---------+--------------+------+--------------+---------+----------+ |
1 | 可以看到当前数据库版本为空,并检测到3个数据升级文件状态都为pending。 |
- 执行migrate命令:
1 2 3 4 5 | G:\flywaydb\demo>flyway migrate Flyway Trial Edition 5.0.7 by Boxfuse Database: jdbc:mysql://localhost:3306/test (MySQL 5.5) Successfully validated 4 migrations (execution time 00:00.103s) ERROR: Found non-empty schema(s) `test` without schema history table! Use baseline() or set baselineOnMigrate to true to initialize the schema history table. |
此时可以看到Flyway报错了,报错信息中可以看到它的提示:数据库中没有找到版本历史表,请使用baseline命令或设置baselineOnMigrate参数为true初始化Flyway版本历史表。接下来我们执行一下baseline命令看一下效果。
- 执行baseline命令,我们使用参数指定当前数据库版本,为与本文的数据库升级文件匹配,这里我们使用参数指定当前数据库版本为1.0.0.0,指定描述为init_sample。命令如下:
1 2 3 4 5 6 | G:\flywaydb\demo>flyway -baselineVersion=1.0.0.0 -baselineDescription=init_sample baseline Flyway Trial Edition 5.0.7 by Boxfuse Database: jdbc:mysql://localhost:3306/test (MySQL 5.5) Creating Schema History table: `test`.`flyway_schema_history` Successfully baselined schema with version: 1.0.0.0 |
可以看到Flyway创建了版本历史表flyway_schema_history并将基线版本标记为1.0.0.0。
- 使用info查看当前数据库状态:
1 2 3 4 5 6 7 8 9 10 11 12 | G:\flywaydb\demo>flyway info Flyway Trial Edition 5.0.7 by Boxfuse Database: jdbc:mysql://localhost:3306/test (MySQL 5.5) Schema version: 1.0.0.0 +-----------+---------+--------------+----------+---------------------+---------+----------+ | Category | Version | Description | Type | Installed On | State | Undoable | +-----------+---------+--------------+----------+---------------------+---------+----------+ | Versioned | 1.0.0.0 | init_sample | BASELINE | 2018-05-11 11:40:07 | Success | No | | Versioned | 1.1.0.0 | feature 8126 | SQL | | Pending | Yes | | Versioned | 1.1.0.1 | bug 8527 | SQL | | Pending | No | +-----------+---------+--------------+----------+---------------------+---------+----------+ |
可以看到当前数据库版本为1.0.0.0,执行过一次baseline操作,并且结果中未展示V1_0_0_0__init_sample.sql文件,因为flyway只会展示比当前版本高的升级文件,不符合Flyway命名规范的文件和版本号低于或等于当前版本的文件都被忽略。
- 执行migrate命令升级数据库版本,此时可以看到数据库从当前版本依次升级至最高版本1.1.0.1。
1 2 3 4 5 6 7 8 | G:\flywaydb\demo>flyway migrate Flyway Trial Edition 5.0.7 by Boxfuse Database: jdbc:mysql://localhost:3306/test (MySQL 5.5) Successfully validated 4 migrations (execution time 00:00.125s) Current version of schema `test`: 1.0.0.0 Migrating schema `test` to version 1.1.0.0 - feature 8126 Migrating schema `test` to version 1.1.0.1 - bug 8527 Successfully applied 2 migrations to schema `test` (execution time 00:01.209s) |
通过上面的实验可以发现使Flyway管理已有数据库只需先使用baseline命令初始化数据库版本或者在Flyway配置文件中配置baselineOnMigrate=true,接下来就可以正常升级数据库了。
回滚数据库
数据库回滚也是数据库版本管理中常见的场景,Flyway使用undo命令执行回滚操作,undo命令执行过程与migrate类似。注意:社区版Flyway不支持undo操作,如果需要数据库回滚功能请下载专业版或企业版。下面演示数据库回滚操作。
- 准备数据库回滚文件:本章仍使用与上面相同的数据库文件,可以看到有一个U1_1_0_0__feature_8126.sql文件,该文件即数据库回滚脚本文件,回滚脚本文件命名规范与升级文件一致,只有前缀不同,回滚文件的前缀缺省为U。
- 使用info命令查看当前数据库状态和版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | G:\flywaydb\demo>flyway info Flyway Trial Edition 5.0.7 by Boxfuse WARNING: You are using the 30 day limited Flyway Trial Edition. After 30 days you must remove th Database: jdbc:mysql://localhost:3306/test (MySQL 5.5) Schema version: 1.1.0.1 +-----------+---------+--------------+----------+---------------------+---------+----------+ | Category | Version | Description | Type | Installed On | State | Undoable | +-----------+---------+--------------+----------+---------------------+---------+----------+ | Versioned | 1.0.0.0 | init_sample | BASELINE | 2018-05-11 11:40:07 | Success | No | | Versioned | 1.1.0.0 | feature 8126 | SQL | 2018-05-11 13:38:27 | Success | Yes | | Versioned | 1.1.0.1 | bug 8527 | SQL | 2018-05-11 13:38:27 | Success | No | +-----------+---------+--------------+----------+---------------------+---------+----------+ |
可以看到当前数据库版本为1.1.0.1,共执行过两次升级和一次baseline操作,需要注意的是版本历史表有一列数据名为Undoable,1.1.0.0这个版本被标记为yes,其他为no,该列即用来标识该版本是否可以回滚,Flyway检测到与升级文件版本号一致的回滚文件时就将该版本标记为可回滚的。
- 使用undo命令执行数据库回滚:
1 2 3 4 5 6 7 | G:\dataman\外汇\技术文档\flywaydb\demo>flyway undo Flyway Trial Edition 5.0.7 by Boxfuse WARNING: You are using the 30 day limited Flyway Trial Edition. After 30 days you must remove this ver Database: jdbc:mysql://localhost:3306/test (MySQL 5.5) Current version of schema `test`: 1.1.0.1 ERROR: Unable to undo migration to version 1.1.0.1 as no corresponding undo migration has been found. |
可以看到Flyway提示错误:当前版本为1.1.0.1,没有找到当前版本对应的回滚文件。执行回滚操作需要由高版本到低版本依次回滚,不能直接回滚某一个低版本,按照提示我们将U1_1_0_0__feature_8126.sql改为U1_1_0_1__feature_8126.sql,与当前版本对应。
- 重新执行undo命令:
1 2 3 4 5 6 7 | G:\flywaydb\demo>flyway undo Flyway Trial Edition 5.0.7 by Boxfuse Database: jdbc:mysql://localhost:3306/test (MySQL 5.5) Current version of schema `test`: 1.1.0.1 Undoing migration of schema `test` to version 1.1.0.1 - feature 8126 Successfully undid 1 migration to schema `test` (execution time 00:01.575s) |
可以看到这次数据库回滚成功了,执行回滚的版本是1.1.0.1。
- 使用info命令查看当前数据库版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | G:\flywaydb\demo>flyway info Flyway Trial Edition 5.0.7 by Boxfuse Database: jdbc:mysql://localhost:3306/test (MySQL 5.5) Schema version: 1.1.0.0 +-----------+---------+--------------+----------+---------------------+---------+----------+ | Category | Version | Description | Type | Installed On | State | Undoable | +-----------+---------+--------------+----------+---------------------+---------+----------+ | Versioned | 1.0.0.0 | init_sample | BASELINE | 2018-05-11 11:40:07 | Success | No | | Versioned | 1.1.0.0 | feature 8126 | SQL | 2018-05-11 13:38:27 | Success | No | | Versioned | 1.1.0.1 | bug 8527 | SQL | 2018-05-11 13:38:27 | Undone | | | Undo | 1.1.0.1 | feature 8126 | UNDO_SQL | 2018-05-11 14:26:11 | Success | | | Versioned | 1.1.0.1 | bug 8527 | SQL | | Pending | Yes | +-----------+---------+--------------+----------+---------------------+---------+----------+ |
可以看到当前数据库版本为1.1.0.0,最后新增了一条数据库回滚的记录。通过本实验可以发现Flyway使用回滚脚本完成数据库回滚,回滚脚本的版本必须要与升级版本一致,且版本号从高到低依次回滚。
总结
Flyway是一个优秀的数据库版本管理工具,简单灵活易操作,每当需要升级数据库时,无论是修改表结构结构(DDL)还是数据(DML),只需创建一个比当前版本号更高的新升级文件。下一次Flyway启动时,它将找到新版本的升级文件并相应地升级数据库。很好的解决了,应用数据库升级的痛点。可以广泛使用在应用升级过程中。
Flyway常用配置
配置 | 说明 |
---|---|
spring.flyway.baseline-description | 对执行迁移时基准版本的描述 |
spring.flyway.baseline-on-migrate | 当迁移时发现目标schema非空,而且带有没有元数据的表时,是否自动执行基准迁移,默认false |
spring.flyway.baseline-version | 开始执行基准迁移时对现有的schema的版本打标签,默认值为1 |
spring.flyway.check-location | 检查迁移脚本的位置是否存在,默认false |
spring.flyway.clean-on-validation-error | 当发现校验错误时是否自动调用clean,默认false |
spring.flyway.enabled | 是否开启flywary,默认true |
spring.flyway.encoding | 设置迁移时的编码,默认UTF-8 |
spring.flyway.ignore-failed-future-migration | 当读取元数据表时是否忽略错误的迁移,默认false |
spring.flyway.init-sqls | 当初始化好连接时要执行的SQL |
spring.flyway.locations | 迁移脚本的位置,默认db/migration |
spring.flyway.out-of-order | 是否允许无序的迁移,默认false |
spring.flyway.password | 目标数据库的密码 |
spring.flyway.placeholder-prefix | 设置每个placeholder的前缀,默认${ |
spring.flyway.placeholder-replacement | placeholders是否要被替换,默认true |
spring.flyway.placeholder-suffix | 设置每个placeholder的后缀,默认} |
spring.flyway.placeholders.[placeholder name] | 设置placeholder的value |
spring.flyway.schemas | 设定需要flywary迁移的schema,大小写敏感,默认为连接默认的schema |
spring.flyway.sql-migration-prefix | 迁移文件的前缀,默认为V |
spring.flyway.sql-migration-separator | 迁移脚本的文件名分隔符,默认__ |
spring.flyway.sql-migration-suffix | 迁移脚本的后缀,默认为.sql |
spring.flyway.tableflyway | 使用的元数据表名,默认为schema_version |
spring.flyway.target | 迁移时使用的目标版本,默认为latest version |
spring.flyway.url | 迁移时使用的JDBC URL,如果没有指定的话,将使用配置的主数据源 |
spring.flyway.user | 迁移数据库的用户名 |
spring.flyway.validate-on-migrate | 迁移时是否校验,默认为true |