版本控制数据库的正确策略

Posted

技术标签:

【中文标题】版本控制数据库的正确策略【英文标题】:Proper strategy to version control database 【发布时间】:2012-09-21 17:49:53 【问题描述】:

我正在阅读这个blog,我对所写的 5 个帖子有疑问。 据我了解,您在包含所有 SQL DDL 语句的大型基线脚本上创建。完成此操作后,您可以在单独的脚本中跟踪每个更改。

但是我不明白脚本文件的名称如何与您的应用程序的特定构建相关联?他说,如果用户报告 3.1.5.6723 中的错误,您可以将脚本重新运行到该版本。您是否会在自己的文件中跟踪对表等的更改,或者在同一个脚本文件中进行所有 DLL 更改,然后按照他的说法在自己的文件中拥有视图等?

【问题讨论】:

【参考方案1】:

首先,数据库升级是邪恶的,但 blog 描述了一场彻头彻尾的噩梦。

可以根据升级方法创建程序员能力矩阵:

0 级:根本没有升级。客户害怕并使用应用程序提供的 UI 或第三方数据库管理解决方案手动移动数据(相信我,这确实是可能的)。 级别 1:有升级数据库转储的脚本。客户感到安全,但他们将在未来 1-2​​ 年内解决微小且非常恼人的问题。系统正在运行,但不允许更改。 2 级:表更改。巨大的停机时间,尤其是在升级过程中出现问题的情况下。巨大的问题,几乎不能保证获得 100% 安全的结果。数据转换由一个有缺陷的脚本管理。客户不高兴。 级别 3:无模式设计:一两个小时的停机时间让错误的脚本翻译数据库中的配置(此步骤在许多情况下可能会损坏数据库)。支持人员的所有咖啡储备都用完了。 4 级:延迟透明升级:零停机时间,但仍可能出现一些问题。客户几乎很高兴,但仍记得以前的经历。 5 级:理想架构,无需显式升级。总幸福。客户不知道升级程序是什么。开发人员富有成效且冷静。

我将描述所有技术问题,但在此之前让我声明以下内容(请原谅我的回答很长):

现在开发周期非常压缩,数据库很大 几乎任何功能都可能引入方案更改并破坏兼容性,因此我们要么有一个简单而稳定的升级过程,要么我们可能会推迟某个功能 客户可能会发现问题,因此有机会进行紧急热修复构建并需要一些升级步骤 一般来说,最好避免您和客户之间的任何障碍

0 级和 1 级 这两种情况都很明显和愚蠢。任何人都应该避免这种情况。

2级 更改对于小桌子来说并没有那么糟糕,但对于大桌子来说可能是个问题。在非常大的表 (>1Gb) 上,完成 ALTER TABLE 可能需要几个小时甚至几天。此外,它确实只解决了模式升级问题,但存储数据呢?我还建议考虑物理数据布局,以了解这种方法背后的实际障碍。整个过程可能不安全,因此请确保您有备份。

解决方案:

复制并重命名:http://www.rndblog.com/don%E2%80%99t-alter-table-do-copy-and-rename/ 表拆分(应用级分区):http://www.mysqlperformanceblog.com/2006/10/08/small-things-are-better/ 分区(有一些限制)

3 级 架构升级的问题是通过将架构移动到更高层来解决的。无模式解决方案有些有限,主要是因为它禁用了关系模型背后的全部功能。可以提出一种混合方法,以同时具有快速升级和使用关系代数的能力。有一些有趣的文章:

http://backchannel.org/blog/friendfeed-schemaless-mysql http://yoshinorimatsunobu.blogspot.com/2010/10/using-mysql-as-nosql-story-for.html

请注意,升级过程的复杂性仍然存在,只是移到了应用程序级别。有许多相关的场景,但我将描述一个我已经使用了几年的混合系统。我可以将数据模型描述为“具有关系的实体”。实体之间的关系在数据库级别表示,实体本身存储为 XML blob。

这个系统很成熟,有足够多的客户。有很多功能请求,所以研发和 QA 团队有点压力。最初的升级过程是作为一个独立的 Java 应用程序实现的,它从 DB 中读取 XML blob,使用 DOM API 对其进行升级并将其写回 DB。实际的方法看起来很简单,但背后有几个隐藏的问题:

升级逻辑可能有点错误,因此可能会写入错误的 XML 数据,从而显着增加客户的停机时间 读取-转换-写入 1-2GB 的 XML 可能需要一些时间 所有升级过程步骤都应包含自动化测试(我会说 CI 是必须的) 可能会在一两天内发现隐藏的故障,因此由于插入了新数据,因此备份不再有用 升级代码可能会变得有点混乱,尤其是如果您希望/需要在构建之间进行升级(任何敏捷团队的正常要求)

我已尝试通过使用更严格的升级程序定义、验证规则和 CI 系统针对真实数据(从所有客户收集)执行的广泛测试来降低所有潜在风险。由于旧升级脚本很久以前引入的旧问题,我很惊讶地看到一些步骤失败。为了解决隐藏的问题,开发了单独的升级步骤。还进行了一些优化以将升级时间减少到合理的 20-30 分钟。基于控制台的进度条实现完成了剩下的工作。

快速说明:任何最终用户都渴望看到任何长时间运行(>2 分钟)操作的进展。请不要忘记实现这样的“乐趣”。

最初 DB 版本存储在单独的表中。请不要使用这种方法,因为单独对实体进行版本化并避免在升级期间锁定整个数据库会更好。

将显示一个升级过程作为示例(所有验证和验证步骤都隐藏在<build/><version/> 处理逻辑之后)。 '-' 表示更少,'*' - 任何构建

<?xml version="1.0"?>
<upgrade>

   <version name="-7.4">
      <build name="*">
        <script class="upgrade.version7.Replace...Script"/>
        <script class="upgrade.version7.Update...Script"/>
         <!-- 5 scripts skipped -->
      </build>
   </version> 
   <version name="-7.6">
      <build name="*">
    <script class="core.DatabaseUpdateVersion" version="7.6.48"/>
      </build>
   </version>
   <version name="7.6">
      <build name="*">
        <script class="upgrade.version7.Update...Script"/>
    <script class="core.DatabaseUpdateVersion" version="8.0.40"/>
         <!-- 7 scripts skipped -->
      </build>
   </version>

   <version name="8.0">
      <build name="-53">... </build>
      <build name="+52">... </build>
   </version>

   <version name="8.1">
      <build name="-8"> ... </build>      
     <build name="-9">...</build>      
     <build name="-26">...</build>      
     <build name="-40">...</build>      
      <build name="-45">...</build>      
      <build name="-56">...</build>      
      <build name="-61">...</build>      
      <build name="-63">...</build>      
      <build name="-64">...</build>      
      <build name="-68">...</build>      
      <build name="-69">...</build>      
      <build name="-77">...</build>      
      <build name="-79">...</build>      
      <build name="-80">...</build>      
      <build name="-86">...</build>      
      <build name="-88">...</build>
      <build name="-89"> ... </build>
   </version> 

   <version name="8.2">...</version>
</upgrade>

每个脚本都是一个小型 Java 或 Groovy 实现(也使用了 XSLT)。 后来还开发了降级程序,但这是完全不同的故事。

4级 应用层的数据方案允许做很多有趣的事情。例如,可以将 XML 替换为 protobuf。像往常一样,这样做有几个原因(它更简单、更快等)。如果您不喜欢建设者的概念,您可以改用thrift。

无论如何,protobuf 允许创建向后兼容的系统(就存储的数据而言)几乎不费吹灰之力。顺便说一句,优势不错。让您的系统向后兼容,您可以轻松实现惰性和完全透明的升级。它可以是后台进程或根据请求进行升级等。好消息是零停机、快乐的用户以及更频繁地进行升级的能力。这意味着您可以快速发展,及时响应客户的要求,换句话说,取得更大的成功。

5 级 抱歉,这次不行。请注意升级策略。销售一个定义了一些模式的系统并把自己锁在外面是很容易的。没有新功能——没有客户。

简单但非常有用的清单:

您能否及时解决来自客户端的问题? 客户升级系统是否安全(如果它至少是关键任务怎么办)? 确定问题需要多长时间? 是否有任何自动验证?

感谢您的阅读。

【讨论】:

感谢您的贡献。 +1 没问题。我只需要与某人分享它,我保存它太久了:) 数据库升级并不是如上所述的大问题。数据库与您的代码一样多的应用程序,因此它意味着改变。只需为每个版本使用升级脚本,确保它已经过测试,创建备份并且您是完全安全的。 @Rein,如果与升级过程相关的问题更少,那就太好了。我刚刚分享了我的经验和我的观点。无论如何,如果说“如果脚本稳定,如果..经过测试,如果数据正常,......”,任何人都怎么能完全安全。任何地方都有可能出现问题。 这就是为什么您应该在开发管道的所有阶段进行备份和测试升级脚本的原因。数据库与您的代码一样多是应用程序,因此它应该是可维护的、易于更改和可测试的。【参考方案2】:

我个人使用liquibase。非常少数的工具。 允许非常复杂的工作流程,例如tutorial, using oracle and complicated versioning scheme

【讨论】:

【参考方案3】:

对表和视图的更改不会保留在它们自己的文件中。您将为所做的每个更改创建一个新的更改脚本。因此,如果您将表 'X' 更改 5 次,那么每次更改都会有 5 个不同的更改脚本。

当您想创建某个版本的架构时,您将获得标记为该版本的源代码。从基线创建数据库。然后按时间顺序在该版本的代码中运行更改脚本。 例如

db_scripts/

2012-01-01 baseline.sql:
create table book (
   book_name varchar(100),
   author_name varchar(100)
)
--- label version_1.0

2012-02-01 add_publisher.sql:
alter table book add column publisher varchar(100)
--- label version_1.1

2012-03-01 add_publish_date.sql:
alter table book add column publish_date datetime
--- label version_1.2

2012-04-01 add_rating.sql:
alter table book add column rating integer
--- label version 1.3

现在假设您要从 1.2 版开始重新创建数据库:

    您可以将代码同步到标签 version_1.2 按baseline.sql、add_publisher.sql、add_publish_date.sql的顺序运行脚本

【讨论】:

正如 Sameer 所说,您将从一个大的 DDL 开始 - 但随后变化将激增。每个编号的脚本,应该是一个或几个表的小前向增量,在一个功能区域中以一个小增量或发布。我们在一个产品中使用了 900+ 版本。 你能用@Sameer的例子详细说明一下吗?【参考方案4】:

您可以使用 Flyway (http://flywaydb.org/) 代替 Liquibase,它允许您编写自己的升级/降级 SQL 脚本。这提供了更大的灵活性,也适用于视图和存储过程。

Liquibase 要求您使用他们自己的基于 XML 的语言进行架构更改,这可能会有些限制。

【讨论】:

您的信息已过期,liquibase 允许在普通 sql 中创建变更集。 liquibase.org/manual/formatted_sql_changelogs【参考方案5】:

在数据库中保留版本号,并在启动时应用更新脚本,是该策略的重要组成部分。

以下是启动的工作原理:

检查数据库中的 DB_VERSION 记录, 发现更新 > 当前版本;也许通过代码。 运行每个适用的“更新”、脚本或编程操作.. DB_VERSION 会在每次更新后更新,因此中途失败可以重新运行。

例子:

当前找到 DB_VERSION = 789; 复杂的代码或大而长的 IF 链会找到 790 及以上的更新。 更新 #790,升级客户和帐户表; 更新#791,升级Email表; 更新#792,重构订单表; 现在的数据库版本 = 792。

有一些注意事项。这合理效果很好;人们声称它应该是 100% 可靠的,但事实并非如此。

脚本不完整、字段长度变化或服务器版本不同等问题有时会导致脚本/SQL 在某些数据库上传递,但在其他数据库上失败。

查找要运行的脚本,可以像一个包含许多 IF 语句的大单一方法一样简单。或者您可以更优雅地通过发现或元数据加载脚本。有时,能够包含编程代码(而不仅仅是 SQL)很有用。

public void runDatabaseUpgrades() 
    if (version < 790) 
      // upgrade Customer and Account tbls
      version = 790;
    
    if (version < 791) 
      // upgrade Email tbl
      version = 791;
    

【讨论】:

以上是关于版本控制数据库的正确策略的主要内容,如果未能解决你的问题,请参考以下文章

Atitit.研发管理---api版本号策略与版本控制

微信小游戏 RES版本控制+缓存策略

使用 TestFlight 进行内部测试时,啥是好的 iOS 应用版本控制策略?

快照/发布:Maven 项目的版本控制策略

如何避免在版本控制中存储密码?

Snowflake 数据仓库中的模式版本控制