手动编写的SQL脚本 对于部署脚本的变更,例如发布内容范围的变更、分支合并及重复劳动等等必须手动完成,并且需要额外的测试。 这种情况下需要维护两种类型的脚本,即对这次发布的创建脚本,以及针对某些特定变更的变更脚本。对于同样的变更需要维护两种脚本,这已是灾难开始的前兆了。 - 确保被执行的部署脚本在运行时能够正确判断环境状态 —— 这一点取决于开发者,以及脚本编写的方式。如果脚本本身只包括相关的变更命令,那么它对于执行时的环境状态就一无所知。这就意味着即使某个列已经存在,它也会试图再次新增这个列。而如果要编写能够在执行期判断环境状态的脚本,会极大地提升脚本开发工作的复杂性。
- 确保部署脚本能够正确处理与合并冲突 —— 虽然基于文件的版本管制系统提供了合并冲突的功能,但这一点对于数据库来说意义不大,因为版本管理库里的内容未必是百分之百准确的,因此也无法充当唯一的信任源。脚本或许覆盖了另一个团队所做的某个hot fix,而这种错误不会留下任何痕迹。
- 只为相关的变更生成部署脚本 —— 脚本的创建是属于开发过程的一部分。根据所布置的任务,如果要确保脚本中只包括相关的、并且经过授权的变更,必需对脚本进行改动,而这进一步提高了部署时的风险,并且也会浪费时间。
- 确保部署脚本了解数据库的依赖项 —— 开发者必须在编写脚本时留意到数据库的依赖项。如果只使用一个唯一的脚本,那么变更通常是按顺序从脚本的最后加入的,这就有可能导致对相同的对象进行多次变更。而且如果要使用多个脚本,那么脚本的执行顺序则至关重要,并且必须手动维护。
结论:这种基本方式不仅无法克服数据库的各种挑战,并且极易出错,也极耗时间,并且需要引入一个额外的系统以跟踪被执行的脚本。
建立一个变更日志的跟踪系统 另一种常见的方式是使用XML文件作为一种抽象的语言,对变更进行描述并对执行过程进行追踪。这方面最常见的开源解决方案就是Liquibase。 Liquibase使用XML文件将逻辑变更从物理变更中分离出来,并且允许开发者在不了解数据库的特定指令的情况下编写变更。在执行期间,它会将XML转化为特定的RDBMS语言以执行这些变更。所有的变更将被组合到一个变更日志中,日志可以作为一个单独的XML文件存在,也可以是由一个包含了变更顺序的主XML文件所引用的多个XML文件共同实现。 可以用现有的基于文件的版本控制系统保存这些XML文件,这一点与基本方式的好处是相同的。此外,通过Liquibase的执行跟踪能力,还能够了解到哪些变更日志是已经被部署过,不应该再次运行的,以及哪些是尚未部署而等待运行的。 那么让我们来看一下,Liquibase是否解决了这些挑战呢: - 确保所有的数据库代码都被涵盖 ——Liquibase中的XML文件不支持对引用内容变更的管理,必须由外部的扩展功能进行处理,这就很可能导致某些变更被遗忘。
- 确保以版本控制库作为唯一的信任源 ——Liquibase本身没有任何版本控制的功能,它依赖于第三方的版本控制工具对XML文件进行管理。因此,你还是必须想办法保证基于文件的版本控制库能够正确地反映当前被测试的数据库版本。为了确保版本控制库能够作为唯一的信任源,开发者必需将变更签入,以便进行测试。这有可能导致尚未完成的变更也被部署到下一个环境中。
- 确保被执行的部署脚本在运行时能够正确判断环境状态 ——Liquibase知道哪些变更日志已经被部署,并且不会再次执行它们。但是,如果某个逻辑变更是增加一个日期类型的列,而该列已经存在,并且是varchar格式的,那么部署肯定会失败。此外,Liquibase也无法避免外部进程对数据库进行的任何变更。
- 确保部署脚本能够正确处理与合并冲突 ——在Liquibase之外对数据库进行的任何变更都可能导致冲突,而这是Liquibase无法处理的。

无法处理外部进程产生的变更- 只为相关的变更生成部署脚本 —— 在变更日志这一级别可以忽略某些变更,但将一个变更日志分解为多个日志需要重写编写XML文件,而这也需要更多的测试。
- 确保部署脚本了解数据库的依赖项 —— 在变更日志XML文件的编写过程中,需要手动维护变更的顺序。
结论:使用能够追踪变更执行的系统并不能处理数据库开发中的所有挑战,最终也无法胜任部署的需求。
建立简单的比较与同步机制 另外一种常见的方式是通过将源(开发)环境与目标(测试、UAT、生产等等)环境进行比较,由此自动生成数据库变更脚本。这种方式节省了开发者与DBA的大量时间,因此他们无需手动地对每次发布的创建脚本或变更脚本进行手动维护了。只在需要的时候生成对应目标环境当前结构的脚本。 让我们再来看一看,这种方式是否能够应对数据库管理的挑战: - 确保所有的数据库代码都被涵盖 —— 多数的比较与同步工具都了解如何处理不同的数据库对象,但其中只有一部分工具能够在比较与同步时处理引用数据。
- 确保以版本控制库作为唯一的信任源 —— 简单的比较与同步工具在执行比较与生成合并脚本的时候,并不会用到代码控制库。
- 确保被执行的部署脚本在运行时能够正确判断环境状态 —— 最佳实践是在准备执行的时候生成脚本,这样就能保证它引用了正确的环境状态了。
确保部署脚本能够正确处理与合并冲突 —— 简单的比较与同步工具将A与B(源与目标)环境进行比较,基于右方的表,该工具能够生成一份脚本,将目标环境进行“升级”,以符合源环境的内容。如果不了解某个变更的内容,那么有可能会生成错误的脚本。举例来说,在目标环境中有一个索引,是在某个不同的分支或是严重缺陷修复时创建的。如果该索引并不存在于源环境中,那么工具又该怎么做呢?删除这个索引?如果在开发环境中存在某个索引,而在生产环境中不存在,是意味着开发环境中加入了这个索引,还是说生产环境中删除了这个索引呢?使用这种工具作为解决方案,需要你对每个变更的内容有深入的了解,以保证能够正确地进行处理。 - 只为相关的变更生成部署脚本 —— 比较与同步工具会对整个数据库schema进行比较,并显示出不同之处。但它们并不了解变更背后的原因,因为这些信息是保存在软件生命周期管理工具、CMS,或是版本控制库中的,而它们对于比较与同步工具来说属于外部信息。结果是你可能会被一大堆无关的背景杂音所干扰,导致你难以判断应该做些什么。
- 确保部署脚本了解数据库的依赖项 —— 比较与同步工具能够了解数据库的依赖项,并且以正确的顺序生成相关的DDL、DCL与DML语句。但不是所有比较与同步工具都支持在生成的脚本中包含多个schema的内容。
结论:比较与同步工具能够满足这些必要需求中的一部分,但不是全部。脚本依然需要手动审查,而且在自动化过程中无法完成依赖。
建立一个数据库执行变更管理解决方案 数据库执行变更管理结合了对数据库对象强制使用版本控制的流程,并且基于版本控制库及当前环境的结构,在需要时生成部署脚本。 这种方式意味着“按需构建与部署”,意即部署脚本是在需要时才进行构建(生成)的,而不是作为开发过程的一部分。这种方式保证了有效地处理冲突、合并,以及外部进程产生的变更。 
按需构建与部署 那么数据库执行变更管理解决方案又是如何应对相同的挑战的呢? - 确保所有的数据库代码都被涵盖 —— 结构、用数据库语言编写的业务逻辑、引用内容、数据库权限等内容都被正确地管理。
- 确保以版本控制库作为唯一的信任源 —— 强制的变更策略能够阻止任何人在任何IDE(甚至是命令行)中对数据库对象进行更改,而不经过事先的签出与变更后的签入。这就保证了版本控制库在对象签入时始终于对象的定义相一致。

|