复杂分支系统中的数据库迁移
Posted
技术标签:
【中文标题】复杂分支系统中的数据库迁移【英文标题】:Database migrations in a complex branching system 【发布时间】:2011-09-18 13:09:08 【问题描述】:在我们当前的开发工作流程中,我们引入了数据库迁移(使用Ruckusing)以保持我们开发人员的数据库架构同步。它工作得很好,使用起来非常简单,但现在我们已经切换到 git 作为 VCS,我们正面临数据库版本控制系统中的下一个问题。
当检查一个已经开发了一段时间的分支时,可能会发生数据库架构与我来自的分支中的架构有很大不同的情况。这在某些情况下会导致数据库冲突。从逻辑上讲,我们似乎需要根据我们之前所在的分支运行迁移,但这会很快变得复杂,并且肯定会遇到一些人的问题。据我所知,没有一个支持分支的数据库迁移系统??
切换到功能分支时会增加复杂性,我们可能需要向上运行一些迁移,而另一些向下运行......从技术上讲,使用我们当前的 dbmigration 脚本这似乎是不可能的,是否有任何合理的替代方案?在一个非常活跃和分支的开发系统中,是否有任何首选的方式来处理数据库迁移?
【问题讨论】:
这就是我所说的潘多拉魔盒。它非常复杂,需要为每个在完整代码库上工作的人提供大量规则和教育。这包括优先合并包含数据库更改等的分支的规则...... 【参考方案1】:我认为增量迁移的整个想法非常糟糕,真的。在像你这样复杂的环境中,它真的不起作用。你可以让它适用于简单的分支模式,但对于任何复杂的东西,这将是一场噩梦。
我现在使用的系统采用了不同的方法:我们无法进行增量迁移,只能从基线重建数据库。在最初的开发过程中,该基线是一个空数据库,而在维护过程中,它是活动数据库的副本(从转储中恢复)。我们只有一堆 SQL 和 XML 脚本,我们将它们应用于基线以获取当前系统(本质上是迁移,但不是为增量运行而设计的)。更新或切换分支非常简单:核对数据库,加载转储以建立基线,运行脚本。
这个过程并不像仅仅运行几次迁移那么快,但它已经足够快了。去喝杯咖啡需要的时间足够长,但吃午饭的时间不够。
巨大的优势在于,从 nuking 数据库开始意味着该过程完全独立于历史记录,因此它不需要知道或关心跨越分支、及时回溯或其他任何事情。
当您发布实时版本时,您所做的事情显然略有不同:您不会破坏数据库或加载转储,因为系统已经处于基线(基线被定义为实时系统的状态! )。您只需运行脚本。之后,制作一个新的转储文件作为新的开发基准。
【讨论】:
这是我们的突破性发现,我以低廉的价格与您分享。实际上,我不知道这种方法是否在我的公司之外广泛使用(实际上,它甚至在我公司内部都不是通用的!)。这不是我在讨论迁移等时遇到的问题。实际上,这对Programmers 来说是个好问题。 对数据库运行“一堆 SQL 和 XML 脚本”与增量迁移有何不同?你的意思是脚本不相互依赖?这听起来与增量迁移系统基本相同(从基线开始,按顺序应用脚本)。 考虑到这一点,我认为,如果我使用 git,我不愿意采用任何使分支更改花费超过几秒钟的东西。很想知道@TomAnderson 和他的团队是否发现这是一个问题。 @Alex 哦,是的,使用这种方法来更改分支永远都吓坏了!最初“足够长,你可以去喝杯咖啡”,但在项目后期,>10 分钟,因为测试数据量很大。所以我们添加了一个缓存层:迁移后,该工具在共享数据库服务器上保存一个转储;在迁移之前,它会检查是否有目标状态的转储,如果有,就加载它。这将常见情况缩短到几秒钟。 也就是说,迁移缓慢是因为我们使用通过 ORM 的非常慢的 ETL 工具加载大量测试数据。如果测试数据是 SQL 脚本,重建速度仍然很快,我们就不需要缓存层了。【参考方案2】:我真的不同意增量迁移被腐烂。在我看来,拥有一套本土脚本会比拥有一个真正的工具来完成这样的工作更糟糕,这样可以更容易地跟踪这些更改。以前我自己也遇到过类似的情况,所以希望我能分享一些见解。
根据我的经验,RDBMS 模式和分支不能很好地混合。根据您的分支,架构可能至少应该有些相似,在这种情况下,迁移不应有太大差异。或者我可能只是误解了问题的全部程度。如果你是例如尝试将客户特定的代码保留在分支上,那么也许您应该考虑一种将其模块化的方法。我们做了类似的事情,有规定客户特定模式更改的规则,并且代码只能依赖于公共代码库,而不是相反。我们还根据模块和日期设置模块变更集之间的优先级,因此我们大部分都知道应用变更的顺序。当然是 YMMV,但在不知道您当前的设置的情况下很难给出具体信息。
在我以前的公司,我们成功地使用了一个名为 Liquibase 的工具,这听起来与您正在使用的类似。基本上,它是一种用于获取数据库模式的工具,以及从一个已知状态到另一个已知状态的所有数据。相同的变更集只应用一次,因为 liquibase 维护一个带有校验和的变更日志。变更日志以特定的 XML 格式编写。如果您需要其他选择,我强烈建议您尝试一下。
无论如何,我们处理客户代码和分支的方式是为给定的分支提供特定的数据库/模式。这样您就可以从分支点获得架构和数据,并且只将差异迁移到当前情况。我们没有撤消更改,即使理论上 liquibase 可以支持这一点,因为我们觉得它太麻烦且容易出错。鉴于 liquibase 保持它自己的状态,迁移总是像在给定分支上获取当前状态一样简单,然后全部应用。仅应用了新的变更集,使架构处于良好状态。
我们使用了mercurial,它是分布式的,就像git一样,所以设置非常相似。我们还在开发笔记本电脑上安装了开发人员特定的本地数据库,以及针对不同客户和阶段(开发、集成、生产)的许多环境,因此该模型经过了实际测试,并且运行得非常好。我们在变更集中有一些冲突,但在引入问题后不久我们就能够解决这些冲突。本地开发环境确实是最难的部分,因为在开发过程中可能已经引入了一些架构更改,这些更改并不总是与后来的更改集兼容,但是更改的结构化性质,并且具有要恢复的已知状态导致很少真正的问题。
这种方法有一些注意事项:
-
对架构的所有和任何更改都必须在更改集中实现。造成混乱的最大原因总是有人只是摆弄了一下。
第一点也适用,即使您使用的是修改架构的工具,例如像 Hibernate 这样的 ORM 工具。您需要非常熟悉此工具才能了解它所做的更改和所需的更改。
所有用户都必须接受这一点,并接受教育以遵守规则。检查 1。
迁移大量 的变更集开始花费太多时间。此时您将需要创建一个新的基线,这可能有点棘手,尤其是在有很多分支的情况下。最好提前计划好,至少知道所有现有的数据库分支。
您需要提前计划一下分支,以了解它们是否会在某个时候迁移回 master。简单的合并可能不适用于模式更改。
对于寿命很长的分支和分离的数据集,此模型可能不够强大
然而,重点是,您对数据库的结构和控制越多,迁移就越容易。因此,Liquibase 之类的工具可能是帮助您跟踪这些更改的真正宝贵资产。这在更大程度上适用于更复杂的模型,而不是简单的模型,所以请至少不要考虑放弃你已经拥有的所有工具。并花一些时间探索其他替代工具。
一些结构和控制总比没有好,甚至更糟,认为你可以控制一大堆手动脚本。
【讨论】:
你说你有“一个给定分支的特定数据库/模式”——你的意思是你有某种数据库的参考副本,你可以复制到开发人员的数据库中吗?或者每个开发人员对每个分支都有一个单独的架构?还是别的什么? 我不确定您为什么认为拥有一大堆脚本无法控制。如果您的团队编写并维护这些脚本,那么您就可以完全控制。您不信任某些第三方工具来正确解决复杂问题,而是您直接负责正在做的事情。【参考方案3】:这是我最近一直在做的事情。对我来说,问题不在于数据库模式本身已经分歧,而是 git 无法将它们合并在一起。涉及数据库模式的功能分支总是很可怕。
我一直在考虑的解决方案是,不是进行线性迁移,而是进行依赖于其他迁移的迁移。你会得到一个很好的迁移依赖图,它很容易线性化(拓扑排序)。只需跟踪数据库中的命名迁移,并以正确的顺序执行尚未更新的更新。
例如,addCustomerSalt
依赖于initialSchema
,separateAddress
依赖于person
。
这没有解决的一个问题是,如果分支 A 依赖于在分支 B 中创建的更新 Z,但也许在这种情况下,您应该重新定位到一个共同的祖先?
【讨论】:
【参考方案4】:我正在考虑在当前项目中进行测试的一种方法是创建一个分支“迁移”,并且所有(且仅)迁移都提交给该分支。开发人员必须在创建迁移之前从该分支合并到当前分支,以便始终在最新迁移之上创建迁移。所有项目都从这个分支合并,因此每个分支都有一个线性迁移历史的概念。这使每个分支都能够在数据库版本之间来回移动。当切换到依赖于不同版本数据库的分支时,开发人员会应用适当的迁移。
烦恼(除了将迁移提交到特殊分支的额外工作和勤奋之外)是记住哪个迁移对应于特定分支。我想这样做的一种方法不是直接将迁移提交到迁移分支,而是将迁移(并且只有迁移)提交到当前分支,然后选择该提交到迁移分支。然后您可以查看当前分支最后一次选择迁移分支的时间,并知道该差异包含必要的迁移。我认为这是可能的。此外,开发人员可能会创建迁移只是为了查看需要进行哪些更改,然后尝试推断适合使用的迁移。
对不起,含糊的建议;如果我们最终尝试了这种方法,我会用更具体的建议来编辑这个建议。
【讨论】:
【参考方案5】:我的情况类似,我在一个实时网站和几个开发分支上工作,我需要在其中更改数据库架构。
我通过编写一个可以很好地与 git 一起使用的 post-checkout 和 post-merge hook 来解决它。我将所有迁移以 SQL 文件的形式存储在一个单独的目录中,并将它们与更改的 php 代码一起提交。每次我执行一个
git checkout
或者一个
git merge
git 会自动调用适当的向上和向下迁移。在Github 上查看我的实现。
作为一个特殊要求(对于那些不想关注 github 链接的人)一些更多的解释:
考虑以下场景。你有两个分支:
master - 包含当前在线的网站 feature - 包含未完成的新功能要使新功能正常工作,需要更改数据库架构。 工作流程如下:
当您在功能分支中更改需要更改的代码时 数据库模式,您还在迁移目录中提交两个新的 SQL 文件, 说:
20151120130200-extra-field-up.sql
(包含要迁移的所有 SQL 查询
向上)
20151120130200-extra-field-down.sql
(包含要迁移的所有 SQL 查询
向下)
当您现在对 master 执行结帐时,post-receive git 挂钩将:
-
在来自
<new HEAD>..<old HEAD>
的提交中找到所有 *-down.sql 脚本
使用本地数据库执行这些脚本
在来自<old HEAD>..<new HEAD>
的提交中找到所有 *-up.sql 脚本
使用本地数据库执行这些脚本
-
在来自
master..feature
的提交中找到所有 *-up.sql 脚本
使用本地数据库执行这些脚本
安装
只需将结帐后和/或合并后文件复制到 .git/hooks 您自己的 git 存储库的目录。您可以编辑这些配置部分 文件。有关说明,请参阅文件本身。
用法
迁移 SQL 文件的命名至关重要。他们应该以
up.sql
或 down.sql
。名称的其余部分完全取决于您。
但是,如果您有一个包含多个向上迁移和/或多个
向下迁移的执行顺序取决于
字典顺序。不同提交中的迁移文件将
始终以与提交相同(反向)的顺序调用。
不需要同时进行升级和升级, 也不需要向上和向下迁移命名相似。
【讨论】:
您应该添加更多详细信息,这些钩子如何工作以及应该如何编写迁移脚本。 (不要只链接到代码和解释,因为该链接将来可能会损坏。)以上是关于复杂分支系统中的数据库迁移的主要内容,如果未能解决你的问题,请参考以下文章
SVN到Git的一键迁移脚本(保留所有分支、Tag及提交记录)