[英]Databases and “branch”
我们目前正在开发一个使用数据库的应用程序。
每次我们更新数据库结构时,我们都必须提供一个脚本来将数据库从先前版本更新为当前版本。
因此,数据库当前有一个数字,它给了我们当前版本,然后我们的软件在我们想要使用“旧”数据库时进行更新。
我们遇到的问题是我们有分支机构:
当我们创建一个新的大功能,用户无法使用(并且不包含在发行版中)时,我们会创建一个分支。
主分支(主干)将定期合并,以确保创建早午餐具有最新的错误更正。
这是一些例子:
问题出在我们的更新脚本上。 它们从先前版本更新为当前版本,然后更新数据库的版本号。
想象一下,在创建分支时我们有数据库版本17。
然后我们执行分支,并在Trunk DB上进行更改。 DB现在版本为18。
然后我们在分支上进行db更改。 由于我们知道已有新版本“18”,我们创建版本19和更新程序18-> 19。
然后在树枝上合并主干。
在这个时刻,我们可能会有一些永远不会运行的更新程序。
如果有人在合并之前更新了他的数据库,他的数据库将被标记为具有版本19,更新17-> 18将永远不会完成。
我们想要改变这种行为,但我们找不到如何:
我们的约束是:
我们可以做些什么来确保数据库分支之间的连续性?
我认为最简单的方法是使用Ruby-on-rails方法。 每个数据库更改都是一个单独的脚本文件,无论多小。 每个脚本文件都有编号,当您进行升级时,只需从数据库当前的数字到最后一个脚本运行每个脚本。
这在实践中意味着你的数据库版本系统停止v18到v19,并开始是v18.0到v18.01,然后是v18.02等。你发布给客户的内容可能会卷入一个大的v19升级脚本,但随着您的发展,您将进行许多小型升级。
您必须稍微修改它以适用于您的系统,每个脚本都必须重新编号,因为它合并到分支,或者您必须确保升级脚本不是简单地跟踪上一个升级号,而是跟踪每个升级号码,因此当脚本合并时,仍会填充缺少的漏洞。
当您创建发布标记(首先在主干上)时,您还必须将这些小升级汇总到下一个主要数字中,以保持理智。
编辑:所以从根本上说,你首先要摆脱使用升级sdcript从版本到版本的概念。 例如,如果您从一个表开始,并且trunk添加了列A并且分支添加了列B,那么您将trunk合并到分支 - 除非分支版本号始终大于trunk的升级脚本,如果随后将trunk合并到分支,则不起作用。 因此,您必须废弃适用于开发分支的“版本”的想法。 唯一的方法是独立更新每个更改,并单独跟踪每个更改。 然后你可以说你需要“最后一个主要版本加上colA plus colB”(诚然,如果你合并了trunk,你可以从trunk获取当前的主要版本,无论是v18还是v19,但你仍然需要单独应用每个分支更新) 。
所以你从DB v18的trunk开始。 分支并进行更改。 然后在以后合并trunk,其中DB位于v19。 您仍然需要应用早期的分支更改(或者应该已应用,但如果重新创建数据库,则可能需要编写分支更新脚本,其中包含所有分支更改)。 请注意,分支根本没有“v20”版本号,并且分支更改不会像在trunk上那样对单个更新脚本进行更改。 如果您愿意,可以将您在分支上所做的这些更改作为单个脚本添加(或者“自上次主干合并后更改”的1个脚本)或许多小脚本。 分支完成后,最后一项任务是对分支进行所有数据库更改并将其转换为可应用于主升级程序的脚本,当它合并到主干上时,该脚本将合并到当前的升级脚本和数据库版本号受到了冲击。
有一种替代方案可能适合您,但是当您尝试使用数据更新数据库时,我发现它有点不稳定,有时它无法进行更新,并且必须擦除并重新创建数据库(公平地说,如果我当时使用SQL脚本,可能会发生这种情况)。 这是使用Visual Studio数据库项目。 这会将架构的每个部分存储为文件,因此每个表只有1个脚本。 这些将由Visual Studio本身隐藏,它将向您显示设计器而不是脚本,但它们将作为文件存储在版本控制中。 VS可以部署项目,如果已存在,将尝试升级您的数据库。 注意选项,许多默认设置说“删除并创建”,而不是使用alter来更新现有表。
这些项目可以生成(主要是机器可读的)SQL脚本用于部署,我们用它们生成这些脚本并将它们交付给不使用VS且只接受SQL的DBA团队。
最后,有Roundhouse这不是我用过的东西,但它可能会帮助你成为新的升级程序“脚本”。 它是一个免费的项目,我读它比VS DB项目更强大,更容易使用。 它是一个数据库版本控制和变更管理工具,与VS集成,并使用SQL脚本。
我们现在使用以下程序约1。5年。 我不知道这是否是最好的解决方案,但我们没有遇到任何麻烦(除了遗漏USE
statement中的delta文件中的一些人为错误)。
它与Krumia给出的答案有一些相似之处,但不同之处在于,在这种方法中只执行新的更改脚本/增量文件。 这使得编写这些文件变得更加容易。
达美文件
写下您为delta文件中的功能所做的所有数据库更改。 您可以在一个增量文件中包含多个语句,也可以将它们拆分为多个。 一旦提交了该文件,它就是最好的(并且一旦合并就必须)开始一个新文件并保持旧文件不受影响。
将所有delta文件放在一个目录中,并为它们指定一个名称模式,如YYYY-MM-DD-HH.mm.description.sql
。 您必须及时对它们进行排序(因此是时间戳),这样您才能知道首先需要执行哪个文件。 除此之外,您不希望与这些文件发生合并冲突,因此它应该是唯一的(在所有分支上)。
合并/拉
创建一个执行以下操作的合并脚本(用于检查bash脚本):
git diff --stat $old_hash..HEAD -- path/to/delta-files
) 通过使用git来确定哪些文件是新的(以及因此当前分支上尚未执行的数据库操作),您不再受版本编号的约束。
交替增量文件
可能会发生在一个合并中,来自不同分支的delta文件可能是“new to execution”,并且这些文件是这样交替的:
当时间戳确定执行顺序时,首先会添加一些来自功能A,然后是功能B,然后再返回到功能A.当您编写正确的增量文件时,可以由他们自己/独立执行,这应该是'是个问题。
我们最近开始使用Sql Server数据工具 (SSDT)替换Visual Studio数据库项目类型,以控制我们的SQL数据库。 它为每个数据库创建一个项目,包含视图和存储过程的项目,以及创建可部署到SQL Server实例的数据层应用程序 (DACPAC)的功能。 SSDT还支持单元测试和静态数据 ,并为开发人员提供了使用LocalDB实例进行快速沙盒测试的选项。 有一个很好的TechEd视频概述SSDT工具和更多的在线资源。
在您的情况下,您将使用SSDT在应用程序代码旁边的版本控制中管理数据库对象,使用相同的合并过程在分支之间推送功能。 在升级现有安装时,您将创建DACPAC并使用数据层应用程序升级过程来应用更改。 或者,您也可以使用数据库同步工具(如DBGhost或RedGate)将更新应用于现有架构。
您想要数据库迁移。 许多框架都有插件。 例如,CakePHP使用CakeDC的插件来管理。 以下是一些通用工具: http : //en.wikipedia.org/wiki/Schema_migration#Available_Tools 。
如果您想自己滚动,可能不是将当前数据库版本保留在数据库中,而是保留已应用哪些补丁的列表。 因此,而不是具有值为19
一行的version
表,而是具有包含多行的patches
表:
Patches
1
2
3
4
5
8
看看这个你需要应用补丁6和7。
这个想法可能有效,也可能无效,但到目前为止,阅读你的工作,以前的答案看起来像重新发明轮子。 “wheel”是源代码控制,具有分支,合并和版本跟踪功能。
目前,对于每个数据库模式更改,您都有一个包含上一个更改的SQL文件。 您已经提到了这种方法存在的重大问题。
用以下方法替换您的方法:维护一个(并且只有一个!)SQL文件,该文件存储用于从头创建表,索引等的所有DDL命令。 你需要添加一个新字段吗? 在SQL文件中添加“ALTER TABLE”行。 这样,您的源代码管理工具实际上将管理您的数据库架构,每个分支可以有不同的。
突然之间,源代码与数据库架构,分支和合并工作同步,等等。
注意:只是为了澄清这里提到的脚本的目的是每次都从头开始重新创建数据库到特定版本。
编辑:我花了一些时间寻找材料来支持这种方法。 这是一个看起来特别好的,具有良好的记录:
你以前见过这种情况吗?
- 您的团队正在围绕数据库编写企业应用程序
- 由于每个人都围绕同一个数据库构建,因此数据库的架构不断变化
- 每个人都有自己的“本地”数据库副本
- 每当有人更改架构时,所有这些副本都需要最新的架构才能使用最新的代码构建
- 每次部署到临时或生产数据库时,架构都需要使用最新版本的代码
- 模式依赖性,数据更改,配置更改和远程开发人员等因素使水变得混乱
您目前如何解决使数据库版本保持正常运行的问题? 你怀疑这需要花费更多的时间吗? 有很多方法可以解决这个问题,答案取决于您环境中的工作流程。 以下文章介绍了一种可以用作起点的简洁方法。
- 由于它可以使用ANSI SQL实现,因此它与数据库无关
- 由于它依赖于脚本,因此它需要可忽略的存储管理,并且它可以适合您当前的代码版本管理程序
我偶然发现了Jeff Atwood在2008年写的一篇较旧的文章; 希望它仍然与您的问题相关。
它提到了由K. Scott Allen撰写的五部分系列:
在我看来,您使用的数据库版本控制方法肯定是错误的。 如果必须有任何版本,它应该是源代码。 源代码有版本。 您的实时环境只是源代码的一个实例 。
答案是使用可重新部署的更改脚本应用数据库更改。
这是一个很难回答的问题,因为您尚未指定要使用的数据库。 所以我将举例说明我的组织是如何做到这一点的。
让我举一个简单的例子:如果我们需要在特定的表中添加一列,我们不只是编写ALTER TABLE ... ADD COLUMN ...
我们编写代码来添加列, 当且仅当给定表中不存在该列时。
现在,我们有单独的API来处理所有存在检查样板代码。 所以我们的脚本只是调用这些API。 你必须自己写。 这些API实际上并不那么难(我们使用的是Oracle RDBMS)。 但它们为我们在版本控制和部署方面带来了巨大的收获。
确实是的。 列的数据类型可以更改; 可以添加一个新表; 属性列可以合并为主键(非常罕见); 序列可以改变; 约束; 外键; 他们都可以改变。
但事实证明,所有这些都可以通过API来处理,并具有读取元数据表的特殊权限。 我并不是说这很容易,但我说这是一次性成本。
我个人的经验是,如果你在敲击键盘以编写ALTER TABLE
语句之前付出了一些实际的努力,这种情况非常罕见。 如果有回滚,你应该手动处理它。 (例如,手动删除添加的列)。
通常,对视图和存储过程的更改很常见,并且对表定义的更改很少。
正如我之前所说,构建数据库可以通过运行所有可重新部署的脚本来完成。 预部署的脚本无效。
您的数据库部署脚本不应以DROP DATABASE
开头。 您的数据库有大量用于单元测试的数据。 除非你制作一个真正非常简单的系统,否则这些数据在未来的测试中将是有价值的。 每次升级数据库时,您的测试人员都不会对为各种表添加一万条记录感到高兴。
抛开测试人员,您打算如何在不消除所有生产数据的情况下升级客户/客户生产数据库? 这就是您必须使用可重新部署的更改脚本的原因。
您可以尝试版本号方案,例如18.1-branchname
等......但它们真的会彻底失败。 因为您可以合并您的源,而不是它的实例。
有专门设计用于处理此类问题的工具。
DBSourceTools是一个GUI实用程序,可帮助开发人员将SQL Server数据库置于源代码管理之下。 功能强大的数据库脚本,代码编辑器,SQL生成器和数据库版本控制工具。 比较Schemas,创建diff脚本,轻松编辑T-SQL。 比Management Studio更好。
另一个: neXtep Designer
NeXtep设计器是数据库开发人员的集成开发环境。 该产品背后的主要概念是利用版本控制来计算您提供开发所需的增量SQL脚本。
该项目旨在构建一个开发平台,该平台提供数据库开发人员所需的所有工具,同时自动执行生成交付的任务(=开发产生的SQL)。
要了解有关提供数据库更新的问题的更多信息,我们邀请您阅读“交付数据库更新”一文,该文章将向您介绍我们对最佳和最差实践的看法。
我认为满足大多数要求的方法是采用“数据库重构”概念。
有一本关于这个主题的好书重构数据库:进化数据库设计
数据库重构是对数据库模式的一个小改动,它改进了它的设计而不改变它的语义(例如,你不添加任何东西,也不破坏任何东西)。 数据库重构的过程是数据库模式的渐进式改进,以便提高您支持客户新需求的能力,支持演化软件开发以及修复现有的遗留数据库设计问题。
本书从以下角度描述了数据库重构:
技术。 它包括如何在数据库级别实现每个重构的完整源代码,对于大多数重构,我们将展示应用程序如何更改以反映数据库中的更改。 我们的代码示例包含Oracle,Java和Hibernate元数据(重构很容易转换为其他环境,有时我们讨论特定于供应商的特性,这些特性简化了一些重构)。
处理。 它详细描述了数据库重构的过程,包括访问数据库的单个应用程序的简单情况,以及许多程序访问的数据库的情况,其中许多程序超出了您的权限范围。 技术示例假设后一种情况,因此如果您处于简单的情况,您可能会发现我们的某些解决方案比您需要的更复杂(幸运的是!)。
文化。 虽然实现单个重构在技术上很简单,并且显然可能(尽管有点复杂)调整您的内部流程以支持数据库重构,但事实是组织内的文化挑战可能是最难克服的障碍。
我认为你提出问题的方式是不可能解决的,但是如果改变你的过程的一部分就有一个解决方案。 让我们从第一部分开始:为什么只使用增量来解决它是不可能的。 在下面我假设你有主干和两个分支DEV-A及DEV-B; 两个分支都来自同一时间点。
说Alice将dev脚本添加到dev-a:
ALTER TABLE t1 (ALTER COLUMN col5 char(4))
和Bob在dev-b中添加另一个脚本
ALTER TABLE t1 (ALTER COLUMN col5 int)
这两个脚本显然是不兼容的,当您从两者中的任何一个合并时,最终会破坏main中的代码。 如果脚本文件具有不同的名称,则合并工具无法提供帮助。
我的建议是根据基线和增量来描述您的数据库:增量脚本必须始终引用特定基线,这样您就能够计算由于将连续增量应用到特定基线而产生的新基线模式。
一个例子
dev-a *--B.A1--D.1@A1--D2@A1--------B.A2--*--B.A3--
/ /
main -- B.0 --*--------------------------*--B.1---*----------
\ /
dev-b *--B.B1--D.1@B1--B.B2--*
请注意,在分支之后,您会立即分拆新的基线,在每次合并之前都是如此。 这样您可以检查基线是否兼容。
在版本控制中管理增量是一种重新发明轮子,因为每个增量脚本在功能上等同于保存不同版本的基线脚本。 这就是说我同意你的看法,他们在实践中传达了更多的价值,迫使人们在改变数据库时思考生产中会发生什么。
如果您选择仅存储基线,则需要大量工具来支持。
另一种选择是序列化数据库上的工作,作为一个整体或在具有唯一所有者的单独区域中对模式进行分区。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.