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

本文涉及的产品
对象存储 OSS,20GB 3个月
云备份 Cloud Backup,100GB 3个月
文件存储 NAS,50GB 3个月
简介: 前言 在数据库代码化(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 策略。

相关文章
|
2月前
|
存储 SQL 关系型数据库
Mysql学习笔记(二):数据库命令行代码总结
这篇文章是关于MySQL数据库命令行操作的总结,包括登录、退出、查看时间与版本、数据库和数据表的基本操作(如创建、删除、查看)、数据的增删改查等。它还涉及了如何通过SQL语句进行条件查询、模糊查询、范围查询和限制查询,以及如何进行表结构的修改。这些内容对于初学者来说非常实用,是学习MySQL数据库管理的基础。
141 6
|
1天前
|
SQL Java 数据库连接
JDBC编程安装———通过代码操控数据库
本文,教你从0开始学习JBCD,包括驱动包的下载安装调试设置,以及java是如何通过JBDC实现对数据库的操作,以及代码的分析,超级详细
|
1月前
|
SQL 关系型数据库 数据库
国产数据实战之docker部署MyWebSQL数据库管理工具
【10月更文挑战第23天】国产数据实战之docker部署MyWebSQL数据库管理工具
144 4
国产数据实战之docker部署MyWebSQL数据库管理工具
|
1月前
|
存储 SQL 数据库
深入浅出后端开发之数据库优化实战
【10月更文挑战第35天】在软件开发的世界里,数据库性能直接关系到应用的响应速度和用户体验。本文将带你了解如何通过合理的索引设计、查询优化以及恰当的数据存储策略来提升数据库性能。我们将一起探索这些技巧背后的原理,并通过实际案例感受优化带来的显著效果。
49 4
|
2月前
|
SQL NoSQL 数据库
Cassandra数据库与Cql实战笔记
Cassandra数据库与Cql实战笔记
42 1
Cassandra数据库与Cql实战笔记
|
28天前
|
JSON JavaScript 关系型数据库
node.js连接GBase 8a 数据库 并进行查询代码示例
node.js连接GBase 8a 数据库 并进行查询代码示例
|
1月前
|
监控 关系型数据库 MySQL
数据库优化:MySQL索引策略与查询性能调优实战
【10月更文挑战第27天】本文深入探讨了MySQL的索引策略和查询性能调优技巧。通过介绍B-Tree索引、哈希索引和全文索引等不同类型,以及如何创建和维护索引,结合实战案例分析查询执行计划,帮助读者掌握提升查询性能的方法。定期优化索引和调整查询语句是提高数据库性能的关键。
294 1
|
2月前
|
SQL 关系型数据库 MySQL
创建SQL数据库的基本步骤与代码指南
在信息时代,数据管理显得尤为重要,其中数据库系统已成为信息技术架构的关键部分。而当我们谈论数据库系统时,SQL(结构化查询语言)无疑是其中最核心的工具之一。本文将详细介绍如何使用SQL创建数据库,包括编写相应的代码和必要的步骤。由于篇幅限制,本文可能无法达到您要求的2000字长度,但会尽量涵盖创建数
115 3
|
1月前
|
监控 关系型数据库 MySQL
数据库优化:MySQL索引策略与查询性能调优实战
【10月更文挑战第26天】数据库作为现代应用系统的核心组件,其性能优化至关重要。本文主要探讨MySQL的索引策略与查询性能调优。通过合理创建索引(如B-Tree、复合索引)和优化查询语句(如使用EXPLAIN、优化分页查询),可以显著提升数据库的响应速度和稳定性。实践中还需定期审查慢查询日志,持续优化性能。
125 0
|
2月前
|
SQL 关系型数据库 MySQL
sql注入原理与实战(三)数据库操作
sql注入原理与实战(三)数据库操作
sql注入原理与实战(三)数据库操作