数据库版本控制Flyway

摘要

在频繁发布版本的情况下,数据库版本难以控制一直是一个头疼的问题,本文主要介绍如何利用Flyway工具实现数据库版本控制。

概述

数据库的版本控制一般都是通过代码管理工具统一管理SQL脚本,但是仅仅是将脚本与代码一起管理,在应用升级时仍然会碰到很多问题:

  • 无法确认在某台机器上的数据库版本

  • 无法确认是否遗漏了某个数据库脚本未执行

  • 无法确认在某台机器上执行过哪些数据库脚本

  • 代码版本库无法管理数据库脚本依赖关系

    所以,我们需要借助专业的数据库版本管理工具实现数据库的版本控制。Flyway就是一个数据库版本控制工具。Flyway主要有以下功能:

  • Flyway可以自动检测指定目录下的数据升级文件并升级至指定版本

  • Flyway可以自动检测指定目录下的数据回滚文件并回滚至指定版本(专业版功能)

  • Flyway可以检测已执行过的数据升级文件是否有改动及是否有错误

  • Flyway可随时展示当前数据库版本和已执行过的数据库升级

  • Flyway可以依照数据库脚本文件命名规则依次执行数据库脚本

Flayway简介

工作原理

  • Flyway通过维护flyway_schema_history表记录数据库版本,升级数据库版本时Flyway将试图查询该表的数据,如果数据库是空的,Flyway将创建flyway_schema_history表。

    图1 创建历史版本表
  • Flyway会按照版本号和执行顺序排序升级历史,执行升级脚本时也会按照版本号从低到高的顺序执行,下图展示了数据库从新建到升级至版本2的过程。
    图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 持续升级
    图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 社区版安装包目录结构如下:
图 4 Flyway-commandline目录结构

配置

执行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目录中的数据库升级文件。
    图5数据库升级文件
  • 使用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。

图6 数据库回滚脚本

  • 使用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