# 数据库脚本管理Flyway

## 什么是Flyway

Flyway是一款开源的数据库版本管理工具，它更倾向于规约优于配置的方式。Flyway可以独立于应用实现管理并跟踪数据库变更，支持数据库版本自动升级，并且有一套默认的规约，不需要复杂的配置，Migrations可以写成SQL脚本，也可以写在Java代码中，不仅支持Command Line和Java API，还支持Build构建工具和Spring Boot等，同时在分布式环境下能够安全可靠地升级数据库，同时也支持失败恢复等。

Flyway主要基于6种基本命令：`Migrate`, `Clean`, `Info`, `Validate`, `Baseline` and `Repair`，稍候会逐一分析讲解。目前支持的数据库主要有：Oracle, SQL Server, SQL Azure, DB2, DB2 z/OS, MySQL(including Amazon RDS), MariaDB, Google Cloud SQL, PostgreSQL(including Amazon RDS and Heroku), Redshift, Vertica, H2, Hsql, Derby, SQLite, SAP HANA, solidDB, Sybase ASE and Phoenix.

*PS：不⽀持分布式数据库，譬如阿⾥云的DRDS*

## Flyway如何工作的?

Flyway对数据库进行版本管理主要由Metadata表和6种命令完成，Metadata主要用于记录元数据，每种命令功能和解决的问题范围不一样，以下分别对metadata表和这些命令进行阐述，其中的示意图都来自Flyway的官方文档。

#### Metadata Table

Flyway中最核心的就是用于记录所有版本演化和状态的Metadata表，在Flyway首次启动时会创建默认名为`SCHEMA_VERSION`的元数据表，其表结构为(以MySQL为例)：

|     Field      |     Type      | Null | Key  |      Default      |
| :------------: | :-----------: | :--: | :--: | :---------------: |
|  version_rank  |    int(11)    |  NO  | MUL  |       NULL        |
| installed_rank |    int(11)    |  NO  | MUL  |       NULL        |
|    version     |  varchar(50)  |  NO  | PRI  |       NULL        |
|  description   | varchar(200)  |  NO  |      |       NULL        |
|      type      |  varchar(20)  |  NO  |      |       NULL        |
|     script     | varchar(1000) |  NO  |      |       NULL        |
|    checksum    |    int(11)    | YES  |      |       NULL        |
|  installed_by  | varchar(100)  |  NO  |      |       NULL        |
|  installed_on  |   timestamp   |  NO  |      | CURRENT_TIMESTAMP |
| execution_time |    int(11)    |  NO  |      |       NULL        |
|    success     |  tinyint(1)   |  NO  | MUL  |       NULL        |

#### Migrate

Migrate是指把数据库Schema迁移到最新版本，是Flyway工作流的核心功能，Flyway在Migrate时会检查Metadata(元数据)表，如果不存在会创建Metadata表，Metadata表主要用于记录版本变更历史以及Checksum之类的。

![img](https://blog.waterstrong.me/assets/flyway-in-practice/command_migrate.png)

Migrate时会扫描指定文件系统或Classpath下的Migrations(可以理解为数据库的版本脚本)，并且会逐一比对Metadata表中的已存在的版本记录，如果有未应用的Migrations，Flyway会获取这些Migrations并按次序Apply到数据库中，否则不需要做任何事情。另外，通常在应用程序启动时应默认执行Migrate操作，从而避免程序和数据库的不一致性。

#### Clean

Clean相对比较容易理解，即清除掉对应数据库Schema中的所有对象，包括表结构，视图，存储过程，函数以及所有的数据等都会被清除。

[![img](https://blog.waterstrong.me/assets/flyway-in-practice/command_clean.png)](https://blog.waterstrong.me/assets/flyway-in-practice/command_clean.png)



Clean操作在开发和测试阶段是非常有用的，它能够帮助快速有效地更新和重新生成数据库表结构，但特别注意的是：不应在Production的数据库上使用！

#### Info

Info用于打印所有Migrations的详细和状态信息，其实也是通过Metadata表和Migrations完成的，下图很好地示意了Info打印出来的信息。

[![img](https://blog.waterstrong.me/assets/flyway-in-practice/command_info.png)](https://blog.waterstrong.me/assets/flyway-in-practice/command_info.png)



Info能够帮助快速定位当前的数据库版本，以及查看执行成功和失败的Migrations。

#### Validate

Validate是指验证已经Apply的Migrations是否有变更，Flyway是默认是开启验证的。

[![img](https://blog.waterstrong.me/assets/flyway-in-practice/command_validate.png)](https://blog.waterstrong.me/assets/flyway-in-practice/command_validate.png)



Validate原理是对比Metadata表与本地Migrations的Checksum值，如果值相同则验证通过，否则验证失败，从而可以防止对已经Apply到数据库的本地Migrations的无意修改。

#### Baseline

Baseline针对已经存在Schema结构的数据库的一种解决方案，即实现在非空数据库中新建Metadata表，并把Migrations应用到该数据库。

[![img](https://blog.waterstrong.me/assets/flyway-in-practice/command_baseline.png)](https://blog.waterstrong.me/assets/flyway-in-practice/command_baseline.png)



Baseline可以应用到特定的版本，这样在已有表结构的数据库中也可以实现添加Metadata表，从而利用Flyway进行新Migrations的管理了。

#### Repair

Repair操作能够修复Metadata表，该操作在Metadata表出现错误时是非常有用的。

[![img](https://blog.waterstrong.me/assets/flyway-in-practice/command_repair.png)](https://blog.waterstrong.me/assets/flyway-in-practice/command_repair.png)



Repair会修复Metadata表的错误，通常有两种用途：

- 移除失败的Migration记录，该问题只是针对不支持DDL事务的数据库。
- 重新调整已经应用的Migratons的Checksums值，比如：某个Migratinon已经被应用，但本地进行了修改，又期望重新应用并调整Checksum值，不过尽量不要这样操作，否则可能造成其它环境失败。

## 如何使用Flyway?

这里将主要关注在Gradle和Spring Boot中集成并使用Flyway，数据库通常会采用MySQL、PostgreSQL、H2或Hsql等。

#### 正确创建Migrations

**Migrations**是指Flyway在更新数据库时是使用的版本脚本，比如：一个基于Sql的Migration命名为`V1__init_tables.sql`，内容即是创建所有表的sql语句，另外，Flyway也支持基于Java的Migration。Flyway加载Migrations的默认Locations为`classpath:db/migration`，也可以指定`filesystem:/project/folder`，其加载是在Runtime自动递归地执行的。

[![img](https://blog.waterstrong.me/assets/flyway-in-practice/sql_migration_base_dir.png)](https://blog.waterstrong.me/assets/flyway-in-practice/sql_migration_base_dir.png)



除了需要指定Location外，Flyway对Migrations的扫描还必须遵从一定的命名模式，Migration主要分为两类：Versioned和Repeatable。

- **Versioned migrations**
  一般常用的是Versioned类型，用于版本升级，每一个版本都有一个唯一的标识并且只能被应用一次，并且不能再修改已经加载过的Migrations，因为Metadata表会记录其Checksum值。其中的version标识版本号，由一个或多个数字构成，数字之间的分隔符可以采用点或下划线，在运行时下划线其实也是被替换成点了，每一部分的前导零会被自动忽略。
- **Repeatable migrations**
  Repeatable是指可重复加载的Migrations，其每一次的更新会影响Checksum值，然后都会被重新加载，并不用于版本升级。对于管理不稳定的数据库对象的更新时非常有用。Repeatable的Migrations总是在Versioned之后按顺序执行，但开发者必须自己维护脚本并且确保可以重复执行，通常会在sql语句中使用`CREATE OR REPLACE`来保证可重复执行。

默认情况下基于Sql的Migration文件的命令规则如下图所示：

[![img](https://blog.waterstrong.me/assets/flyway-in-practice/sql_migration_naming.png)](https://blog.waterstrong.me/assets/flyway-in-practice/sql_migration_naming.png)



其中的文件名由以下部分组成，除了使用默认配置外，某些部分还可自定义规则。

- prefix: 可配置，前缀标识，默认值`V`表示Versioned，`R`表示Repeatable
- version: 标识版本号，由一个或多个数字构成，数字之间的分隔符可用点`.`或下划线`_`
- separator: 可配置，用于分隔版本标识与描述信息，默认为两个下划线`__`
- description: 描述信息，文字之间可以用下划线或空格分隔
- suffix: 可配置，后续标识，默认为`.sql`

## 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. R版本的脚本编写，需要编写者注意控制脚本操作的幂等性.

4. SQL文件命名参考：

   V20190110.10.23__V1.0_Create_Table.sql

   V20190110.10.55__V1.0_Init_Data.sql

   R20190115.20.00__V1.0_Update_Modulexxx.sql  （按模块修改）

   R20190117.21.00__V1.0_Update_Modulexxx.sql  （按模块修改）

   V20190201.17.59__V1.1_Create_Table.sql （新版本）

   