乐趣区

关于sql:Flyway让数据库版本管理更简单

作者:烧鸡太子爷

起源:恒生 LIGHT 云社区

Flyway 是什么

随着我的项目 CICD 接入, 一键启动, 麻利开发曾经成为降本提效的不二法宝, 其中波及 SQL 的变更还不够智能和自动化, 因而亟需一款工具可能帮忙开发及运维人员高效简便地实现 SQL 变更,Flyway 正是能够满足咱们需要的一款工具。

当咱们关上 Flyway 的官网,在首页能够看到对于 Flyway 的一段英文介绍:

Flyway 是数据库的版本控制,跨所有环境的持重架构演变。轻松、欢快和简略的 SQL。

总的来说,Flyway 是一款专为 CI/CD 打造的,能对数据库变更做版本控制的工具。

Flyway 反对的数据库很多,支流的数据库都可能完满反对,官网摘抄如下:

Supported databases are Oracle, SQL Server (including Amazon RDS and Azure SQL Database), Azure Synapse

(Formerly Data Warehouse), DB2, MySQL (including Amazon RDS, Azure Database & Google Cloud SQL), Aurora MySQL,

MariaDB, Percona XtraDB Cluster, TestContainers, PostgreSQL (including Amazon RDS, Azure Database, Google Cloud

SQL, TimescaleDB, YugabyteDB & Heroku), Aurora PostgreSQL, Redshift, CockroachDB, SAP HANA, Sybase ASE,

Informix, H2, HSQLDB, Derby, Snowflake, SQLite and Firebird.

Flyway 解决的问题

在我的项目或产品研发过程中,很难一开始就把业务理分明,把代码逻辑和数据库表设计好,因而代码和数据表也会在迭代周期内一直迭代。咱们都习惯应用 SVN 或者 Git 来对代码进行版本治理,次要是为了解决多人开发代码抵触和版本回退的问题。

其实,数据库的变更也须要版本控制,在日常开发和环境部署中,咱们常常会遇到上面的问题:

  • 在开发环境部署程序发现报错,定位是本人写的 SQL 脚本忘了在以后环境执行导致;
  • 从 Git 上新 down 下来的代码运行报错,定位发现是其余共事批改了的 SQL 脚本没有在以后环境执行导致;
  • 每次公布包都须要公布 SQL 文件包和应用程序的版本包;
  • 线上环境部署报错,发现是运维没有依照你投产文档外面阐明的 SQL 脚本执行程序操作执行导致;
  • 流水线能够自动化部署程序,然而 SQL 脚本还是须要手动执行或者流水线执行 SQL 脚本配置比拟繁琐;
  • 其余场景 ….

有了 Flyway, 这些问题都能够轻松的解决。Flyway 能够对数据库进行版本治理,能够主动执行 SQL,能疾速无效地用于迭代数据库表构造,并保障部署到测试环境或生产环境时,数据表都是保持一致的;

Flyway 次要工作流程

Flyway 工作流程如下:

1、我的项目启动,应用程序实现数据库连接池的建设后,Flyway 主动运行。

2、首次应用时,Flyway 会创立一个 flyway_schema_history 表,用于记录 sql 执行记录。

3、Flyway 会扫描我的项目指定门路下 (默认是 classpath:db/migration) 的所有 sql 脚本,与 flyway_schema_history 表脚本记录进行比对。如果数据库记录执行过的脚本记录,与我的项目中的 sql 脚本不统一,Flyway 会报错并进行我的项目执行。

4、如果校验通过,则依据表中的 sql 记录最大版本号,疏忽所有版本号不大于该版本的脚本。再依照版本号从小到大,一一执行其余脚本。

在 SpringBoot 我的项目应用 Flyway

Flyway 既反对应用客户端 command-lineclient 命令行形式也反对 JAVA API 形式降级数据库,本文只介绍 JAVA API 以 Maven 引入插件形式的应用,更多形式能够查看官网;

引入依赖

在 start.spring.io 上新建一个 SpringBoot 工程,引入数据库驱动依赖等,同时引入 Flyway 的依赖,这个步骤比较简单,不做过多阐明(本文示例应用的是 Mysql 数据库);

<dependency>

    <groupId>org.flywaydb</groupId>

    <artifactId>flyway-core</artifactId>

    <version>7.14.0</version><!-- 截至发稿前,官网最新版本是 7.14.0 -->

</dependency>

增加 Flyway 配置

spring:

  # 数据库连贯配置

  datasource:

    driver-class-name: com.mysql.cj.jdbc.Driver

    url: jdbc:mysql://localhost:3306/xukj_flyway?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT

    username: flyway

    password: xxx

  flyway:

    # 是否启用 flyway

    enabled: true

       # 编码格局,默认 UTF-8

    encoding: UTF-8

    # 迁徙 sql 脚本文件寄存门路,默认 db/migration

    locations: classpath:db/migration

    # 迁徙 sql 脚本文件名称的前缀,默认 V

    sql-migration-prefix: V

    # 迁徙 sql 脚本文件名称的分隔符,默认 2 个下划线__

    sql-migration-separator: __

    # 迁徙 sql 脚本文件名称的后缀

    sql-migration-suffixes: .sql

    # 迁徙时是否进行校验,默认 true

    validate-on-migrate: true

    # 当迁徙发现数据库非空且存在没有元数据的表时,主动执行基准迁徙,新建 schema_version 表

    baseline-on-migrate: true

创立 SQL 脚本

我的项目创立当前,在 src/resources 下有 db/migration(或 db.migration)目录,在其中创立对应的 SQL 文件,基于约定因为配置的准则,不同的类型通过文件命名形式进行辨别

Flyway 对数据库的所有更改都称为迁徙;版本迁徙 (Versioned Migrations) 以 V 结尾,只会执行一次;回退迁徙 (Undo Migrations) 以 U 结尾,执行一旦产生破坏性更改,就会很麻烦,我的项目中个别不必;可反复执行迁徙 (Repeatable Migrations) 以 R 结尾,每次批改后都会从新执行。

总结如下:

  1. 仅须要被执行一次的 SQL 命名以大写的 ”V” 结尾,前面跟上 ”0~9″ 数字的组合, 数字之间能够用“.”或者下划线 ”_” 宰割开,而后再以两个下划线宰割,其后跟文件名称,最初以.sql 结尾。比方,V2020.00.000_1__create_table.sql,V202001.00.000_2__insertTable.sql,V2.1.5__create_table.sql。
  2. 可反复运行的 SQL,则以大写的“R”结尾,前面再以两个下划线宰割,其后跟文件名称,最初以.sql 结尾。比方,R__addTable.sql,R__update_user.sql。
  3. 版本号须要惟一,否则 Flyway 执行会报错;如果 V__脚本.sql,曾经执行过了,不能批改外面的内容,再次执行 Flyway 就会报错。R——脚本.sql, 如有变动能够执行屡次。
  4. V 结尾的 SQL 执行优先级要比 R 结尾的 SQL 优先级高。

如下,咱们筹备了三个脚本,别离为:

1、V2020.00.000_1__create_table.sql,代码如下,目标是创立一张表,且只执行一次

CREATE TABLE `test_flyway_table` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',

  `time` datetime NOT NULL COMMENT '创立工夫',

  PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2、V202001.00.000_2__insertTable.sql,代码如下,目标是往表中插入一条数据,且只执行一次

INSERT INTO `test_flyway_table` VALUES ('1', '2021-06-28 17:48:48');

3、R__addTable.sql,代码如下,目标是每次启动如果有变动,则执行一次

update `test_flyway_table` set time = '2021-08-23 17:48:48' where id =1;

对应目录截图如下:

运行

依照下面配置实现,曾经足够咱们开始运行了,此时,咱们第一次启动我的项目,如果配置没有谬误,运行截图如下:

此时,咱们刷新数据库,能够看到 Flyway 的历史记录表曾经生成并插入了三个版本的记录:

而且,表也曾经创立好了并有一条数据:

此时不做任何扭转,重启程序,日志如下:

日志显示表没有变动,不做变更,查看两张表的内容也无变动。

如果咱们批改 V202001.00.000_2__insertTable.sql 脚本,重启程序,就会报错,提示信息如下:

Caused by: org.flywaydb.core.api.exception.FlywayValidateException: Validate failed: Migrations have failed validation

Migration checksum mismatch for migration version 202001.00.000.2

-> Applied to database : 1190158040

-> Resolved locally: 843339817. Either revert the changes to the migration, or run repair to update the schema history.

如果咱们批改 R__addTable.sql 脚本,重启程序,脚本会再次执行,并且 Flyway 的历史记录表也会减少本次执行的记录。

Flyway 的执行效率

为了验证 Flyway 我的项目数据库迭代了较久工夫,积攒了几百个 Sql 当前是否会拖慢我的项目的启动速度?进行了一个对照组试验:

场景(sql 文件管制在 10K 以内) 执行均匀时长(单位: 秒)
放 1 个曾经被执行过的 SQL 脚本, 重复启动我的项目 11.1
放 25 个曾经被执行过的 SQL 脚本, 重复启动我的项目 11.4
放 50 个曾经被执行过的 SQL 脚本, 重复启动我的项目 11.6
放 100 个曾经被执行过的 SQL 脚本, 重复启动我的项目 12.19

脚本在历史记录表中有记录,即便有几百条 SQL 脚本,每次我的项目启动只执行单次迭代的脚本,所以耗时次要来源于两个方面:

  • Flyway 顺次读取脚本中内容时的 IO 开销;
  • Flyway 计算脚本 checksum 值的算法开销;

对于 IO 开销而言,每个脚本如果不是波及大量的数据变更, 只是表构造的变更, 脚本的大小都十分小, 能够不思考. 事实上 Flyway 也不适宜大量的数据变更时应用, 因而 IO 开销对启动耗时的增量根本能够疏忽

Flyway 计算 checksum 值采纳的是驰名的 CRC32(循环冗余校验码)算法,该算法是数据通信畛域中最罕用的一种查错校验码,其特色是信息字段和校验字段的长度能够任意选定。循环冗余查看(CRC)是一种数据传输检错性能,对数据进行多项式计算,并将失去的后果附在帧的前面,接管设施也执行相似的算法,以保障数据传输的正确性和完整性。为了验证算法计算效率,做了如下试验:

场景 执行均匀时长(单位: 毫秒)
读取一个超大文件, 约 1M, 并进行计算,重复执行屡次 48
读取一个失常大小的 SQL 脚本, 约 10K, 并进行计算,重复执行屡次 4

由此可见, 即使是有上百个失常大小的 sql, 计算 checksum 值也不会消耗太多的工夫, 根本都能够在 1 秒内实现, 所以接入 Flyway 后也不用放心会有历史包袱。

退出移动版