• 【实战】Flyway迁移指南最佳实践


    项目在多环境迭代开发过程中,数据库的表结构不断变更,在部署时,往往会出现数据库表结构未及时变更导致出现问题,耗费在表结构上的时间相当多,上线过程持续痛苦,代码有 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 进行初始化,再启动项目即可,非新环境,直接启动项目即可

    示例代码

    参考

  • 相关阅读:
    Python:安装mssql模块功能,并实现与sqlserver连接、查询
    SQLSERVER:Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.
    SQLSERVER:大容量导入数据时保留标识值 (SQL Server)
    SQLSERVER:sqlserver2008r2安装好后,自动提示功能不可以使用
    SQLSERVER:通过sys.tables实现批量删表、快速统计多表记录和
    数据库调优过程(五):物理分表,及写入方案调整
    Greenplum:学习资料
    c#:排序
    Struts(八):动态方法调用
    数据库调优过程(四):提高数据库写入修改方案
  • 原文地址:https://www.cnblogs.com/vcmq/p/13194976.html
Copyright © 2020-2023  润新知