实战flyway迁移指南最佳实践(代码片段)

vcmq vcmq     2022-11-30     253

关键词:

项目在多环境迭代开发过程中,数据库的表结构不断变更,在部署时,往往会出现数据库表结构未及时变更导致出现问题,耗费在表结构上的时间相当多,上线过程持续痛苦,代码有 GIT/SVN 来控制,数据库中的表版本也可以做到版本控制,本文讲解通过 flyway 的方式来管理数据库版本变动。

本文首发个人技术博客:http://nullpointer.pw/flyway-best-practice.html

项目痛点

一个项目单个环境迭代开发的过程中,对于数据库表的修改 DDL,可以通过版本控制工具一起进行控制。只需要在项目上线之前,人工执行新增的 DDL 即可,DDL 的版本是与当前项目迭代版本一致,细致点不至于出现问题。

单个环境版本迭代,数据库的版本号变更流程如下图:

技术图片

对于偏企业服务的公司而言,同一个项目会同时部署到多套环境当中。随着项目迭代进行,不同环境的项目版本可能并非是同步一致的,甚至因为有的环境需要定制化开发,出现同一个项目多个分支,代码也愈行愈远。

多个环境版本迭代,数据库的版本号变更流程如下图:

技术图片

于是在这种情况下,上线服务之前就很痛苦,要想起上线环境的当前表版本是多少,想不起来,就要对比线上库里的表,判断是否执行过了增量的 DDL,每个环境的增量 DDL 都可能是不同的,需要针对每个环境写不同的 DDL,发布时战战兢兢地生怕漏了执行哪个版本的 DDL 导致线上 Bug。

那如何解决这种糟糕的情况呢?

理想状态:项目启动时自动维护数据库版本到最新,不需要人工处理 DDL,避免出错。

Flyway 就提供了达到这种理想状态的功能。

先说一下 Flyway 的原理。

开发者将每个版本的 DDL 放到项目中,项目在新环境启动时,会自动创建一张表用于记录 DDL 的版本信息,随后自动执行未执行过的 DDL,同时将执行过的 DDL 信息存入元数据表中。下次再启动时,检测到执行过了,就不会重复执行。

本文环境

  • SpringBoot 2.1.3.RELEASE
  • Flyway 5.2.1

迁移步骤

  1. 引入依赖
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
    <version>6.4.4</version>
</dependency>

注:如果 springboot 版本低于 2.0.0,最好使用 5.2.1 版本的 flyway-core

  1. 添加 flyway 配置文件

    spring:
      flyway: # flyway 数据库 DDL 版本控制
        enabled: true # 正式环境才开启
        clean-disabled: true # 禁用数据库清理
        encoding: UTF-8
        locations: classpath:/db
        #  flyway 会在库中创建此名称元数据表,用于记录所有版本演化和状态,同一个库不同项目可能冲突,每个项目一张表来记录
        table: flyway_schema_history_FlywayExample #TODO 值的后缀指定为当前项目名称
        baseline-version: 1 # 基线版本默认开始序号 默认为 1
        baseline-on-migrate: true #  针对非空数据库是否默认调用基线版本,为空的话默认会调用基线版本
        placeholders: # 定义 afterMigrateError.sql 要清理的元数据表表名
          flyway-table: $spring.flyway.table
    

    flyway 在启动的时候会自动创建一张名称为 flyway_schema_history 的元数据表,如果多个项目连接的是同一个数据库,会产生冲突影响,所以需要每个项目都有一张自己的元数据表,指定 spring.flyway.table 的值即可,可以指定为 flyway_schema_history_项目名称,这样基本可以做到不会发生冲突了。

  2. 项目 resource 目录添加文件夹.

    创建上一步中 spring.flyway.locations 中指定值的目录,本文是创建 db 目录.

  3. 项目 SQL 迁移.

    SQL 迁移这里有两种情况,第一种是当前项目在所有环境都是初次部署,即数据库中尚未有任何当前项目的表,这种情况很好处理,主要讲一下非初次部署的情况 SQL 迁移步骤。

    1. 先 dump 一份所有环境中当前项目最新版本的表结构,在 resources/db目录中创建一个 base_init.sql 文件,将最新版本的 DDL 以及需要初始化的数据放到这个文件中,这个 sql 文件后期就不要做任何修改。

    2. resources/db 目录增加一个名为 V1__init.sql的文件,内容为空,用于占位

    3. 将所有环境的表结构都统一到 base_init.sql这个版本

    4. 如果有新增的 DDL,则创建一个高版本的 sql 文件,如V2__add_table.sql,项目启动的时候会自动执行 sql,但是不会执行 V1 版本的,所以添加了 V1 版本的用于占位。注意如果新增的 DDL 版本没有执行出错,切勿修改!!!

    5. sql 文件的命名具有一定规则,以V开头,接着两个下划线 __,接着可以写注释,然后以 .sql 结尾,如V3__alter_table.sql 版本号支持小版本x.y.z格式,但是为了简单起见,直接用一个数字递增更方便。

    6. 如果需要部署到新的环境,则只需要执行 base_init.sql中 DDL 即可,其他版本的 DDL 交给 flyway 就可以了

    7. Over~

    8. 有时候如果新版本的 DDL 写错了,可能会导致 flyway 执行失败,会在元数据表中增加一条执行 status 为 0 的记录,只要 status 有为 0 的记录,项目就无法启动,这样就很难受,网上解决方式多是手动去数据库删除这条记录,这未免太危险,可以利用 flyway 的 callback 来实现执行失败,自动删除失败记录。在 resources/db目录下添加名为 afterMigrateError.sq文件,文件内容为

      -- SQL 执行失败,清理 flyway 元数据表中失败的执行记录
      DELETE IGNORE FROM `$flyway-table` WHERE success = 0;
      

      其中的变量就是当前项目元数据表的表名称。

    9. 如果当前项目在所有环境都是初次部署,那就不需要 base_init.sql,初始化直接放到 V1__init.sql 当中,上线时不再需要手动执行 SQL,全部交由 flyway 来执行即可。如果数据库比如测试环境存在经常手动修改表增加表的情况,需要关闭 flyway,存在 flyway 因为在手动执行 SQL 执行之后再执行导致执行失败的情况,所以某个环境使用了 flyway 控制版本之后,就不要再手动增删改表。

    10. Over~~~

常见问题

  1. 出现 java.sql.SQLException: sql injection violation, comment not allow : CREATE TABLE xxxxxx.flyway_schema_history_xxx
    检查是否使用的是 druid ,错误原因是建表语句中包含了 SQL 注释,druid 默认会拦截包含注释的 SQL 执行,需要修改 druid 配置,允许注释。(不知道 flyway 为什么要把注释写到建表语句中)
spring:
  datasource:
    druid:
      # ...... 省略其他
      filter:
        stat:
          enabled: true
        slf4j:
          enabled: true
        wall:
          enabled: true
          config:
            comment-allow: true
#     filters: stat,wall,slf4j 注释此行,filter改成上面的格式

总结

针对多环境迁移流程

  1. 所有环境数据库表版本统一到最新版本
  2. 将最新版本 DDL 放到 base_init.sql
  3. 后续迭代在 resource/db 目录下增加新版本的 DDL 文件
  4. 如果是新环境,先通过 base_init.sql 进行初始化,再启动项目即可,非新环境,直接启动项目即可

示例代码

参考


Flyway 最佳实践:一个大型迁移脚本与多个增量脚本

】Flyway最佳实践:一个大型迁移脚本与多个增量脚本【英文标题】:Flywaybestpractice:onelargemigrationscriptvsmanyincrementalones【发布时间】:2021-12-0120:37:37【问题描述】:我的任务是使用flyway将一些新的迁移脚本添加到现有应用程序中,... 查看详情

markdownvuejs最佳实践指南(代码片段)

查看详情

markdown用python开发的“最佳实践最佳”(bobp)指南。(代码片段)

查看详情

markdown用python开发的“最佳实践最佳”(bobp)指南。(代码片段)

查看详情

markdown用python开发的“最佳实践最佳”(bobp)指南。(代码片段)

查看详情

markdown用python开发的“最佳实践最佳”(bobp)指南。(代码片段)

查看详情

markdown用python开发的“最佳实践最佳”(bobp)指南。(代码片段)

查看详情

markdown用python开发的“最佳实践最佳”(bobp)指南。(代码片段)

查看详情

markdown用python开发的“最佳实践最佳”(bobp)指南。(代码片段)

查看详情

redis技术探索「数据迁移实战」手把手教你如何实现在线+离线模式进行迁移redis数据实战指南(scan模式迁移)(代码片段)

...参考我之前的前两篇文章。【Redis技术探索】「数据迁移实战」手把手教你如何实现在线+离线模式进行迁移Redis数据实战指南(在线同步数据)【Redis技术探索】「数据迁移实战」手把手教你如何实现在线+离线模式... 查看详情

vue开发实战生态篇#18:vuex最佳实践(代码片段)

说明【Vue开发实战】学习笔记。核心概念State一this.$store.state.xxx取值——mapState取值Getter一this.$store.getters.xxx取值——mapGetters取值Mutation一this.$store.commit("xxx")赋值——mapMutations赋值Action一this.$store.dispatch(" 查看详情

redis技术探索「数据迁移实战」手把手教你如何实现在线+离线模式进行迁移redis数据实战指南(在线同步数据)(代码片段)

从实战出发使用RedisShake进行Redis数据在线+离线模式迁移指南RedisShake基本介绍RedisShake是基于redis-port基础上进行改进的是一款开源的Redis迁移工具,支持Cluster集群的在线迁移与离线迁移(备份文件导入)。数据可平... 查看详情

设计模式——软件api设计最佳实践指南小结(代码片段)

文章大纲引言一、面向对象的设计原则1、开闭原则(★★★★★)2、依赖倒转原则(★★★★★)3、里氏替换原则(★★★★)4、合成复用原则(★★★★)5、单一职责原则(★★★★)... 查看详情

设计模式——软件api设计最佳实践指南小结(代码片段)

文章大纲引言一、面向对象的设计原则1、开闭原则(★★★★★)2、依赖倒转原则(★★★★★)3、里氏替换原则(★★★★)4、合成复用原则(★★★★)5、单一职责原则(★★★★)... 查看详情

redis技术探索「数据迁移实战」手把手教你如何实现在线+离线模式进行迁移redis数据实战指南(数据检查对比)(代码片段)

...端Redis的数据将会不一致。【Redis技术探索】「数据迁移实战」手把手教你如何实现在线+离线模式进行迁移Redis数据实战指南࿰ 查看详情

2019工作总结(代码片段)

...用、源码与拓展详解Zabbix企业级分布式监控系统第2版Nginx实战基于Lua语言的配置、开发与架构详解Redis开发与运维Kubuernetes权威指南第4版Kubernetesinaction(中文版)再也不踩坑的Kubernetes实战指南Kubernetes进阶实战Kubernetes网络权威指... 查看详情

mongo实战之数据空洞的最佳实践(代码片段)

问题背景:某天,开发部的同事跑过来反映:mongodb数据文件太大,快把磁盘撑爆了!其中某个db占用最大(运营环境这个db的数据量其实很小)分析:开发环境有大量测试的增/删/改操作,而由于MongoDB顺序写的原因,在我们删除部分无用... 查看详情

.netcore开发实战(定义api的最佳实践)sourcegenerators版(代码片段)

前言极客时间上的《.NETCore开发实战》是一门非常好的课程,作者肖伟宇在第31课(https://time.geekbang.org/course/detail/100044601-201165)介绍了定义API的最佳实践。大意如下:Controller这一层负责与前端用户的交互,它... 查看详情