36

在我们当前的开发工作流程中,我们引入了数据库迁移(使用Ruckusing)以保持我们开发人员的数据库模式同步。它工作得很好,使用起来非常简单,但现在我们已经切换到 git 作为 VCS,我们面临着数据库版本控制系统中的下一个问题。

当检查一个已经开发了一段时间的分支时,可能会发生数据库模式与我来自的分支中的模式有很大不同的情况。这在某些情况下会导致数据库冲突。从逻辑上讲,我们似乎需要根据我们之前所在的分支运行迁移,但这会变得非常复杂,并且肯定会遇到一些人的问题。据我所知,没有一个支持分支的数据库迁移系统?

切换到功能分支时会增加复杂性,我们可能需要向上运行一些迁移,而其他向下迁移......从技术上讲,使用我们当前的 dbmigration 脚本似乎是不可能的,是否有任何明智的选择?在一个非常活跃和分支的开发系统中,是否有任何首选的方式来处理数据库迁移?

4

5 回答 5

29

我真的不同意增量迁移被腐烂。在我看来,拥有一套本土脚本会比拥有一个真正的工具来完成这样的工作更糟糕,因为这样可以更容易地跟踪这些更改。以前我自己也遇到过类似的情况,所以希望我能分享一些见解。

根据我的经验,RDBMS 模式和分支不能很好地混合。根据您的分支,架构可能至少应该有点相似,在这种情况下,迁移不应有太大差异。或者我可能只是误解了问题的全部程度。例如,如果您试图将客户特定的代码保留在分支上,那么也许您应该考虑一种将其模块化的方法。我们做了类似的事情,有规定客户特定模式更改的规则,并且代码只能依赖于公共代码库,而不是相反。我们还根据模块和日期设置模块变更集之间的优先级,因此我们大部分都知道应用变更的顺序。当然是 YMMV,但在不知道您当前的设置的情况下很难给出具体信息。

在我以前的公司,我们成功地使用了一个名为Liquibase的工具,这听起来与您正在使用的类似。基本上,它是一种用于获取数据库模式的工具,以及从一个已知状态到另一个已知状态的所有数据。相同的变更集只应用一次,因为 liquibase 维护一个带有校验和的变更日志。变更日志以特定的 XML 格式编写。如果您需要其他选择,我强烈建议您尝试一下。

无论如何,我们处理客户代码和分支的方式是为给定的分支提供特定的数据库/模式。这样您就可以从分支点获得架构和数据,并且只将差异迁移到当前情况。我们没有撤消更改,即使理论上 liquibase 可以支持这一点,因为我们觉得它太麻烦且容易出错。鉴于 liquibase 保持它自己的状态,迁移总是像在给定分支上获取当前状态一样简单,然后全部应用。仅应用了新的变更集,使架构处于良好状态。

我们使用了分布式的mercurial,就像 git 一样,所以设置非常相似。我们还在开发笔记本电脑上安装了开发人员特定的本地数据库,以及针对不同客户和不同阶段(开发、集成、生产)的多个环境,因此该模型经过了真正的测试,效果出奇地好。我们在变更集中有一些冲突,但在引入问题后不久我们就能够解决这些冲突。本地开发环境确实是最难的部分,因为在开发过程中可能已经引入了一些架构更改,这些更改并不总是与后来的更改集兼容,但是更改的结构化性质,并且具有要恢复的已知状态导致很少真正的问题。

这种方法有一些注意事项:

  1. 对模式的所有和任何更改都必须在变更集中实现。造成混乱的最大原因总是有人只是摆弄了一下。
  2. 第一点也适用,即使您使用的是修改架构的工具,例如像Hibernate这样的 ORM 工具。您需要非常熟悉此工具才能了解它所做的更改和所需的更改。
  3. 所有用户都必须接受这一点,并接受教育以遵守规则。检查 1。
  4. 当迁移大量变更集开始花费太多时间时,就会出现一个问题。此时您将需要创建一个新的基线,这可能有点棘手,尤其是在有很多分支的情况下。最好提前计划好,至少知道所有现有的数据库分支。
  5. 您需要提前计划一下分支,以了解它们是否会在某个时候迁移回 master。简单的合并可能不适用于模式更改。
  6. 对于寿命很长的分支和分离的数据集,这个模型可能不够强大

然而,关键是,您对数据库的结构和控制越多,迁移就越容易。因此,像Liquibase这样的工具可能是一种非常有价值的资产,可以帮助您跟踪这些变化。这在更大程度上适用于更复杂的模型,而不是简单的模型,所以请至少不要考虑放弃你已经拥有的所有工具。并花一些时间探索其他替代工具。

一些结构和控制总比没有好,甚至更糟,认为你可以控制一大堆手动脚本。

于 2011-06-20T19:57:10.983 回答
16

我认为增量迁移的整个想法非常糟糕,真的。在像你这样复杂的环境中,它真的不起作用。你可以让它适用于简单的分支模式,但对于任何复杂的东西,这将是一场噩梦。

我现在使用的系统采用了不同的方法:我们无法进行增量迁移,而只能从基线重建数据库。在最初的开发过程中,该基线是一个空数据库,而在维护过程中,它是活动数据库的副本(从转储中恢复)。我们只有一堆 SQL 和 XML 脚本,我们将它们应用于基线以获取当前系统(本质上是迁移,但不是为增量运行而设计的)。更新或切换分支非常简单:核对数据库,加载转储以建立基线,运行脚本。

这个过程不像只运行几次迁移那么快,但它已经足够快了。你可以去喝杯咖啡需要很长时间,但吃午饭的时间不够。

巨大的优势是,从 nuking 数据库开始意味着该过程完全独立于历史记录,因此它不需要知道或关心跨越分支、及时回溯或其他任何事情。

当您发布实时版本时,您显然做的事情略有不同:您不会核对数据库或加载转储,因为系统已经处于基线(基线被定义为实时系统的状态!)。您只需运行脚本。在那之后,做一个新的转储作为开发的新基线。

于 2011-06-20T10:46:56.797 回答
3

我处于类似的情况,我在一个实时网站和几个需要更改数据库模式的开发分支上工作。

我通过编写一个可以很好地与 git 一起使用的 post-checkout 和 post-merge hook 来解​​决它。我将所有迁移以 SQL 文件的形式存储在一个单独的目录中,并将它们与更改的 PHP 代码一起提交。每次我执行一个

git checkout

或一个

git merge

git 将自动调用适当的向上和向下迁移。请参阅我在Github上的实现。

作为一个特殊要求(对于那些不想关注 github 链接的人)一些更多的解释:

考虑以下场景。你有两个分支:

  • master - 包含当前在线的网站
  • feature - 包含一个未完成的新功能

为了使新功能正常工作,它需要更改数据库模式。工作流程如下:

  1. 当您在功能分支中更改需要更改数据库架构的代码时,您还会在迁移目录中提交两个新的 SQL 文件,例如:

    • 20151120130200-extra-field-up.sql(包含要向上迁移的所有 SQL 查询)
    • 20151120130200-extra-field-down.sql(包含所有向下迁移的 SQL 查询)
  2. 当您现在执行结帐到 master 时,post-receive git 挂钩将:
    1. 在来自的提交中找到所有 *-down.sql 脚本<new HEAD>..<old HEAD>
    2. 使用本地数据库执行这些脚本
    3. 在来自的提交中找到所有 *-up.sql 脚本<old HEAD>..<new HEAD>
    4. 使用本地数据库执行这些脚本
  3. 当您将功能分支合并到主分支时,合并后挂钩将:
    1. 在来自的提交中找到所有 *-up.sql 脚本master..feature
    2. 使用本地数据库执行这些脚本

安装

只需将签出后和/或合并后文件复制到您自己的 git 存储库的 .git/hooks 目录中。您可以编辑这些文件的配置部分。有关说明,请参阅文件本身。

用法

迁移 SQL 文件的命名至关重要。它们应该以 up.sqlor结尾down.sql。名称的其余部分完全取决于您。但是,如果您有一个包含多个向上迁移和/或多个向下迁移的提交,则执行它们的顺序取决于字典顺序。不同提交中的迁移文件将始终以与提交相同(相反)的顺序被调用。

不需要向上迁移和向下升级,也不需要向上和向下迁移命名相似。

于 2015-11-21T10:12:37.080 回答
1

我正在考虑在我们当前的项目中进行测试的一种方法是创建一个分支“迁移”,并且所有(并且只有)迁移都提交给这个分支。开发人员必须在创建迁移之前从该分支合并到当前分支,以便始终在最新迁移之上创建迁移。所有项目都从这个分支合并,因此每个分支都有一个线性迁移历史的概念。这使每个分支都能够在数据库版本之间来回移动。当切换到依赖于不同版本数据库的分支时,开发人员会应用适当的迁移。

烦恼(除了将迁移提交到特殊分支的额外工作和勤奋之外)是记住哪个迁移对应于特定分支。我想这样做的一种方法不是直接将迁移提交到迁移分支,而是将迁移(并且仅迁移)提交到当前分支,然后选择该提交到迁移分支。然后您可以查看当前分支最后一次选择迁移分支的时间,并知道该差异包含必要的迁移。我认为这是可能的。此外,开发人员可能会创建迁移只是为了查看需要进行哪些更改,然后尝试推断适合使用的迁移。

很抱歉提出含糊的建议;如果我们最终尝试这种方法,我会用更具体的建议编辑这个建议。

于 2013-03-01T09:04:50.817 回答
0

这是我最近一直在做的事情。对我来说,问题不在于数据库模式本身已经分歧,而是 git 无法将它们合并在一起。涉及数据库模式的功能分支总是很可怕。

我一直在考虑的解决方案是,不是进行线性迁移,而是进行依赖于其他迁移的迁移。你会得到一个很好的迁移依赖图,它很容易线性化(拓扑排序)。只需跟踪数据库中的命名迁移,并以正确的顺序执行尚未更新的更新。

例如,addCustomerSalt取决于initialSchemaseparateAddress取决于person

这没有解决的一个问题是,如果分支 A 依赖于在分支 B 中创建的更新 Z,但也许在这种情况下,您应该重新定位到一个共同的祖先?

于 2012-08-15T08:56:41.050 回答