数据库代码化(Database-as-Code)实战(二)

本文涉及的产品
对象存储 OSS,20GB 3个月
对象存储 OSS,恶意文件检测 1000次 1年
对象存储 OSS,内容安全 1000次 1年
简介: 前言 在数据库代码化(Database-as-Code)实战一文中介绍了如何借助 Flyway 实现 migration based 的数据库迁移。但在实践过程中,发现了如下问题: 随着项目的发展,迁移脚本数量会越来越多,而全新部署时由于要执行所有的历史变更,部署时间会越来越长。

前言

数据库代码化(Database-as-Code)实战一文中介绍了如何借助 Flyway 实现 migration based 的数据库迁移。但在实践过程中,发现了如下问题:

  1. 随着项目的发展,迁移脚本数量会越来越多,而全新部署时由于要执行所有的历史变更,部署时间会越来越长。
  2. 由于数据库的最终状态是由变更脚本依次执行形成的,这就导致了开发人员无法通过源码直观看到数据库的当前状态。
  3. 因为很多数据迁移场景涉及到字段的解析以及和第三方系统或工具的交互,使用 Python 脚本实现迁移过程会更加方便。但目前 Flyway 只支持执行 SQL 类型的迁移脚本。

为了解决上述问题,我们基于 migration based 方法,并借鉴了 Flyway 的设计思想,改进了原有的数据库代码化方案。

数据库代码化改进方案

迁移脚本命名规范

naming_convention

迁移脚本命名规范参考了 Flyway 的标准,但也增加了一些限制,下面对其进行说明:

  • Prefix - 固定为V
  • Version - 由日期和索引组成,格式固定为yyyy.mm.dd.index。其中 index 长度固定为 3,范围是 000 ~ 999,用于区分当天新增的不同迁移脚本。
  • Separator - 固定为两个下划线__
  • Description - 描述信息,文字之间可以用下划线或空格分隔。
  • Suffix - 后缀标识,支持.sql.py

元数据表结构

和所有 migration based 方案类似,该方案会在目标数据库中创建一个名为schema_version_history的元数据表用于记录变更信息,具体表结构如下。

mysql> describe schema_version_history;
+--------------+--------------+------+-----+-------------------+-------+
| Field        | Type         | Null | Key | Default           | Extra |
+--------------+--------------+------+-----+-------------------+-------+
| version      | bigint(20)   | NO   | PRI | NULL              |       |
| description  | varchar(200) | YES  |     | NULL              |       |
| installed_on | timestamp    | NO   |     | CURRENT_TIMESTAMP |       |
| type         | varchar(20)  | YES  |     | NULL              |       |
+--------------+--------------+------+-----+-------------------+-------+

下面对各字段的含义作简要说明。

  1. version - 已成功应用到数据库上的迁移脚本的版本信息,字段值会从迁移脚本名中提取。例如,2019.11.11.003会被转换成整数20191111003进行存储。
  2. description - 已成功应用到数据库上的迁移脚本的描述信息,字段值会从迁移脚本名中提取。
  3. installed_on - 迁移脚本成功应用到数据库上的时间。
  4. type - 迁移脚本的类型标识(NORMAL、LEGACY、PRE INSTALL、POST INSTALL、UPGRADE EMPTY、OUT OF ORDER)。

迁移脚本组织结构

改进方案的迁移脚本组织结构如下:

|--{db1}
     |--db.conf
     |--init_scripts
          |--a.sql
          |--b.sql
          |...
          `--z.sql
     |--upgrade_legacy_private_cloud_scripts
          |--V0666.00.00.000__alter_TB_a_add_column.sql
          |--V0666.00.00.001__change_to_new_index.py
          `--V0666.00.00.002__alter_TB_c_add_column.sql
     |--upgrade_scripts
          |--V2019.11.11.000__alter_TB_b_add_column.sql
          |--
          |--V2019.11.11.001__TB_c_insert.sql
          `--V2019.11.13.000__migrate_legacy_alert_rule.py
 |--{db2}
      |--db.conf
      ...
 |--common
      |--procedure.sql
      `--schema_version_history.sql

下面对其进行说明:

  1. 每个数据库对应一个独立的目录,包含了该数据库的迁移脚本和配置信息。
  2. 数据库目录下的文件{db}/db.conf包含了该数据库的连接、认证等信息。
  3. 子目录 init_scripts 用于存放数据库的最新 schema。
  4. 子目录 upgrade_legacy_private_cloud_scripts 用于存放专有云老版本到新版本的迁移脚本。版本号需要小于全新部署时的前置版本号10000000000
  5. 子目录 upgrade_scripts 统一存放公有云和专有云的后续迁移脚本。版本号由当前日期和索引组成,大于全新部署时的后置版本号20000000000

可以看到,和原方案相比,新方案有如下改变:

  1. 增加了 SQL 文件common/schema_version_history.sql用于初始化元数据表。
  2. 去掉了用于存放存量 schema 的目录{db}/base_scripts
  3. 新建目录{db}/init_scripts用于存放数据库的最新 schema,全新部署时将直接执行该目录下的 SQL 脚本,免去了执行所有历史变更的过程。
  4. 支持执行 SQL 和 Python 类型的迁移脚本。

执行流程

基于上述迁移脚本的管理模式,公有云和专有云不同场景的执行流程如下:
process

版本号编制

为了方便处理公有云、专有云各类新老版本的部署和升级场景,通过如下方法对版本号进行编制。

  • 数据库全新安装前置版本号设为10000000000
  • 数据库全新安装后置版本号设为20000000000
  • 专有云老版本到新版本的迁移脚本版本号小于10000000000,例如06660000007
  • 公有云和专有云新增的迁移脚本以当前期间和索引作为版本号,大于20000000000,例如20191111003

全新安装 or 升级

不能单纯根据数据库是否为空判断当前应该执行全新安装步骤还是升级步骤,因为程序有可能在执行全新安装步骤时创建了若干张表后异常退出。这里采用的方案如下:

  1. 在执行 init_scripts 中的脚本之前,向元数据表schema_version_history中插入一条 version 为10000000000的记录。
  2. 如果 init_scripts 中的脚本全部执行成功,则将 upgrade_scripts 目录中脚本的最新 version 插入schema_version_history中。
  3. 如果 upgrade_scripts 目录为空,则向schema_version_history中插入一条 version 为20000000000的记录。

这样即使程序中途退出,再次启动后只要发现数据库的版本为10000000000,就继续执行全新安装的步骤。

脚本的可重入性

每一个迁移脚本的成功执行都对应着schema_version_history中的一条记录。如果迁移脚本是 SQL 文件,并且是单纯的 DML,则可以将迁移脚本和迁移记录的插入封装在一个事务中执行,从而避免出现状态不一致。但对于包含 DDL 的 SQL 或是 Python 类型的迁移脚本,显然无法通过事务保证迁移脚本和迁移记录的插入同时成功或失败。因此,这里采用了先执行迁移脚本,再进行迁移记录插入的策略。这就对迁移脚本的可重入性提出了要求。让脚本具备可重入性的通用方法可参考幂等性实践

迁移脚本执行时机

应用升级过程中的数据迁移可能发生在多个阶段,下图展示了某个常见的升级场景。

db_migration

  1. 应用开始升级前需要进行一些表结构的变更(数据迁移),支持应用升级后数据以新的格式写入。
  2. 应用的升级过程是分批次灰度进行的,此时数据有可能以旧的格式写入。
  3. 应用的全部实例完成升级后,需要对升级过程中产生的旧数据进行订正(数据迁移)。

如果严格按 version 大小判断脚本是否需要执行,则有可能出现数据修正脚本无法执行的情况。为此,我们将迁移脚本分成了 pre_upgrade 和 post_upgrade,对于 post_upgrade 中的脚本,只要在schema_version_history中不存在对应的执行记录,就允许它执行。

总结

和原方案相比,改进后的方案让全新部署场景下数据库的初始化时间不会随着迁移脚本的增加而延长,同时也可以通过源码直观看到数据库的当前状态,另外也支持了 Python 类型迁移脚本的执行。但这些改进也是有一定代价的,它要求开发人员在进行数据库变更时,既要增加迁移脚本,也要修改数据库初始化脚本。为了防止开发人员的遗漏,建议对数据库代码化部分执行更加严格的代码合入和代码 review 策略。

相关文章
|
18天前
|
SQL NoSQL Java
彻底革新你的数据库操作体验!Micronaut数据访问技巧让你瞬间爱上代码编写!
【9月更文挑战第10天】Java开发者们一直在寻找简化应用程序与数据库交互的方法。Micronaut作为一个现代框架,提供了多种工具和特性来提升数据访问效率。本文介绍如何使用Micronaut简化数据库操作,并提供具体示例代码。Micronaut支持JPA/Hibernate、SQL及NoSQL(如MongoDB),简化配置并无缝集成。通过定义带有`@Repository`注解的接口,可以实现Spring Data风格的命名查询。
39 6
|
19天前
|
前端开发 数据库
数据库表设计生成代码
BizWorks ToolKit插件集成Mybatis-Plus代码生成工具,支持从数据库表生成代码,便于研发过程中数据模型变更后的代码同步。本文介绍批量生成代码的方法、配置说明及项目示例。配置文件`*.mp.yaml`用于描述生成行为,可放置于`src/main/resource/bizworks/mybatis-plus/`路径下。配置包括数据库信息、输出目录及包名等。通过IDEA右键菜单即可启动代码生成。具体配置和示例详见文档。
24 2
|
20天前
|
前端开发 数据库 开发者
数据模型(数据库表设计)生成代码
BizWorks ToolKit 插件集成 Mybatis-Plus 代码生成工具,支持从数据库表批量生成代码,简化开发流程。本文详细介绍配置方法及项目示例,包括配置文件格式、生成选项及具体操作步骤,帮助开发者快速实现代码同步更新。配置文件 `.mp.yaml` 支持自定义输出目录、生成组件等,适用于多种项目结构。
29 0
|
20天前
|
关系型数据库 数据库 网络虚拟化
Docker环境下重启PostgreSQL数据库服务的全面指南与代码示例
由于时间和空间限制,我将在后续的回答中分别涉及到“Python中采用lasso、SCAD、LARS技术分析棒球运动员薪资的案例集锦”以及“Docker环境下重启PostgreSQL数据库服务的全面指南与代码示例”。如果你有任何一个问题的优先顺序或需要立即回答的,请告知。
39 0
|
24天前
|
SQL 安全 数据库
基于SQL Server事务日志的数据库恢复技术及实战代码详解
基于事务日志的数据库恢复技术是SQL Server中一个非常强大的功能,它能够帮助数据库管理员在数据丢失或损坏的情况下,有效地恢复数据。通过定期备份数据库和事务日志,并在需要时按照正确的步骤恢复,可以最大限度地减少数据丢失的风险。需要注意的是,恢复数据是一个需要谨慎操作的过程,建议在执行恢复操作之前,详细了解相关的操作步骤和注意事项,以确保数据的安全和完整。
56 0
|
28天前
|
JSON 数据格式 Java
化繁为简的魔法:Struts 2 与 JSON 联手打造超流畅数据交换体验,让应用飞起来!
【8月更文挑战第31天】在现代 Web 开发中,JSON 成为数据交换的主流格式,以其轻量、易读和易解析的特点受到青睐。Struts 2 内置对 JSON 的支持,结合 Jackson 库可便捷实现数据传输。本文通过具体示例展示了如何在 Struts 2 中进行 JSON 数据的序列化与反序列化,并结合 AJAX 技术提升 Web 应用的响应速度和用户体验。
72 0
|
28天前
|
开发者 前端开发 JavaScript
如何解锁Play Framework的模块化力量?揭秘构建未来Web应用的关键策略
【8月更文挑战第31天】在现代软件开发中,模块化设计对于构建可维护和可扩展的应用程序至关重要。Play Framework作为一个高性能Web应用框架,提供了强大的模块化支持,使开发者能够将应用分解为独立且可重用的模块。本文将探讨Play Framework中模块化设计的最佳实践,并通过示例代码展示如何创建模块、使用依赖注入管理模块依赖、模块化前端资源及测试模块,帮助开发者构建结构清晰、易于维护和扩展的应用程序。
27 0
|
28天前
|
数据库 Java 数据库连接
玩转Play Framework的秘密武器:Ebean ORM带你解锁高效数据库操作新姿势,让你的代码从此飞起来!
【8月更文挑战第31天】Play Framework 以其简洁的 API 和高效开发体验著称,Ebean ORM 则是其推荐的对象关系映射(ORM)工具之一。Ebean 可将 Java 对象轻松映射到数据库表,简化数据库交互。本文将指导你在 Play Framework 中使用 Ebean ORM 进行数据库操作,涵盖项目创建、依赖引入、数据库配置、模型定义及 CRUD 操作,并通过示例代码展示实现过程。通过这些步骤,你将学会如何利用 Ebean 的丰富功能,如事务管理、查询构建等,提升 Web 应用的数据库交互能力。
28 0
|
28天前
|
数据库 开发者
从EF6无缝切换到Entity Framework Core:一份详尽无遗的开发者实战攻略,带你领略数据库操作的全新境界,让代码优雅转身,性能与可维护性双丰收的秘密武器
【8月更文挑战第31天】本文通过详细的代码示例,介绍了如何将基于 EF6 的应用程序平滑迁移到 EF Core。从创建初始 EF6 项目并定义数据库上下文开始,逐步演示了如何使用 EF6 进行数据操作。随后,文章详细讲解了迁移到 EF Core 的步骤,包括配置 EF Core 数据库上下文、定义领域模型及数据操作等。通过具体示例,展示了 EF Core 的强大功能,帮助开发者构建高效且可扩展的数据访问层。
26 0
|
28天前
|
SQL 数据库 索引
SQL 编程最佳实践简直太牛啦!带你编写高效又可维护的 SQL 代码,轻松应对数据库挑战!
【8月更文挑战第31天】在SQL编程中,高效与可维护的代码至关重要,不仅能提升数据库性能,还降低维护成本。本文通过案例分析探讨SQL最佳实践:避免全表扫描,利用索引加速查询;合理使用JOIN,避免性能问题;避免使用`SELECT *`,减少不必要的数据传输;使用`COMMIT`和`ROLLBACK`确保事务一致性;添加注释提高代码可读性。遵循这些实践,不仅提升性能,还便于后期维护和扩展。应根据具体情况选择合适方法并持续优化SQL代码。
27 0