数据库的版本控制方案
1. 简介
作为软件开发工作的一部分对数据库进行建模是在项目的早期,但随着开发的进展,数据库模型会存在修改。在项目后期也可能由于客户提出的变更导致数据库模型的修改。许多看似不起眼的改动都可能给系统带来严重的后果。
我们希望有一种方法可以将数据库模型的修改和部署更新尽可能的平滑,把对系统可能的影响降到最低,并且即使发生问题也可以快速定位和修复,不造成毁灭的影响。
2. 数据库部署问题
软件开发中数据库的更改和部署有下面常见的一些问题。
- 数据库生产部署可能由团队外部的DBA完成,他们不熟悉我们开发的应用程序,也可能不了解数据库结构
- 手动部署容易出现人为错误
- 手动部署通常需要等到发版窗口时间
- 可能会向其他人提供不正确的脚本,从而导致部署错误的代码
- 数据库的多个副本存在于不同的开发人员计算机上,因此很难确定要使用和部署的正确版本
3.数据库源代码控制示例
假如我们有一个名为 dept 的表。这是创建它的脚本:
CREATE TABLE dept (
id NUMBER,
name VARCHAR(100)
);
我们会将此文件保存为 “dept.sql” 文件,将其添加到我们的存储库中,提交并将其推送到远程代码存储库。
如果想对数据库进行更改怎么办,例如我们增加一个 status 字段?这引出了一个我们该决定如何管理数据库代码的问题。
有两个方案,是基于状态的版本控制和基于迁移的版本控制。
- 方案一 基于状态
修改原有的 detp.sql 脚本。每当需要对表进行更改(例如添加状态列)时,它就会得到更新。
CREATE TABLE dept (
id NUMBER,
name VARCHAR(100),
status VARCHAR(20)
);
- 方案二 基于迁移
创建另一个新的脚本 customer_add_status.sql。一个脚本创建客户表,另一个脚本添加状态列。
ALTER TABLE dept ADD COLUMN status VARCHAR(20);
4. 基于状态 VS 基于迁移
基于状态和基于迁移是数据库版本控制的两种方法。
基于状态:当存储反映数据库当前状态的数据库代码时,称为基于状态的版本控制。可以使用版本控制中的代码来创建数据库,脚本表示当前数据库定义是什么。
基于迁移:当存储反映为到达数据库当前状态而采取的步骤的数据库代码时,称为基于迁移的版本控制。版本控制中的脚本反映用于创建对象的原始脚本,以及后续所有变化的过程脚本。
基于状态的好处是
- 被认为更简单,因为只有一组脚本(例如,每个对象一个脚本,或一些其他划分)。
- 对象的定义在一个地方,所以任何人都可以打开它看看它应该是什么样子。
- 版本控制提交历史将告诉我们更改了什么、何时以及谁更改了它们。
基于迁移的好处是
- 数据库可以建立到某一时间点的状态,这有助于在有多个环境和分支的团队中使用
- 所有更改都在单独的脚本中,因此知道什么正在更改以及何时更改
- 由于脚本更小,部署可能更容易
数据库代码应该反映数据库的当前状态,还是应该反映为达到数据库的当前状态而采取的步骤呢?采用哪种方法需要与团队一起来讨论利弊。
通常的建议是使用基于迁移的部署,好处可能更多一些。这带来的另一个问题是可能会有大量过程脚本,当有大量过程脚本或者部署执行脚本带来大量的时间消耗的时候,我们可以用重新确定数据库脚本基线的方式来处理。
5. 重新确定数据库脚本的基线
重新基线化数据库部署脚本涉及将所有数据库部署脚本组合成一组反映数据库当前状态的脚本。这可以减少对同一对象上的数据库所做的更改数量,从而减少部署时间。
例如,假设在一年的时间里,对表进行了几次更改:
- 从原始脚本创建表
- 添加新列
- 添加索引
- 更改列的数据类型
- 再加三列
- 删除列
- 将表拆分为两个表
在所有这些脚本之后,很难判断表的样子。可以重新设置脚本基线,而不是为每个部署运行所有这些脚本。这意味着您可以创建一个脚本来创建当前状态的数据库,而不是运行所有这些迁移。
6. Liquibase & Flyway
使用比较广泛的数据库迁移工具有 Liquibase 和 Flyway,这两种工具都可以实现基于迁移的数据库部署工作。有开源的免费版本和付费商业版本。
- Flyway https://flywaydb.org
- Liquibase https://www.liquibase.org
7. 一些建议原则
下面是一些建议的指导原则。
- 脚本一旦进入代码库,就不能更改。
一个脚本被添加到版本控制后,如果想对数据库进行更改,无论多小,都要编写一个新脚本。不要更改旧脚本。
- 使用脚本而不是手动更改数据库。
所有更改都应使用相同的过程完成:编写新的脚本文件并在部署过程中获取它。这有助于避免数据库中的意外错误更改并确保一致性。
- 确保每个开发人员都有自己的本地数据库,而不是共享的开发数据库。
请确保团队每个人都有自己的本地开发数据库。
- 避免脚本中的独占锁。
编写的脚本可能会自动或手动在它们正在处理的表上放置独占锁。尽量避免在部署脚本中这样做,因为这会减慢部署过程。不过,这取决于使用的是哪个数据库供应商,因为某些数据库在某些情况下具有表级锁,而另一些则没有。
8. 如何实现上述方案
在团队中实现这一点可能需要一定的时间。技术方面可能需要设置工具和流程。个人方面也可能需要说服人们遵守一些列约定,并在过程中建立信任。
这是从手动数据库部署过程到自动化过程可以采取的步骤的列表。
- 考虑从一个小的项目和团队开始。
- 为数据库生成基线模式。这可以手动完成(编写 SQL 脚本)或使用IDE生成。
- 将数据库脚本添加到源代码管理。
- 确定用于数据库更改和迁移的工具。
- 实现所选工具来处理版本控制的脚本。
- 为每个开发人员实现一个本地数据库
- 更新部署过程(或创建一个)以从源代码控制中的脚本生成数据库
- 更新部署过程以将脚本部署到下一个环境(例如 QA )
- 实施自动化数据库测试。这可能是一大步。
- 更新部署过程以部署到您的下一个环境(例如 Pre Prod)
- 更新部署过程以允许用户在需要时(以及测试通过时)触发部署到生产环境
- 更新 部署过程以自动部署到生产环境。