数据库架构版本管理 101






4.43/5 (6投票s)
同步数据库架构版本的入门方法。
引言
您以前是否见过这种情况?
- 您的团队正在围绕一个数据库开发企业应用程序
- 由于每个人都在围绕同一个数据库进行开发,因此数据库的模式经常处于变化之中
- 每个人都有自己的“本地”数据库副本
- 每次有人更改模式时,所有这些副本都需要最新的模式才能与最新的代码生成版本一起使用
- 每次部署到暂存或生产数据库时,模式都需要与最新的代码生成版本兼容
- 模式依赖项、数据更改、配置更改和远程开发人员等因素会使情况更加复杂
您目前是如何解决这个问题以保持数据库版本正常工作的?您是否怀疑这花费了不必要的时间?处理这个问题的方法有很多,答案取决于您环境中的工作流程。以下文章描述了一种精炼而简单的处理方法,您可以将其作为起点。
- 由于它可以与 ANSI SQL 一起实现,因此它与数据库无关
- 由于它依赖于脚本,因此它只需要极少的存储管理,并且可以集成到您现有的代码版本管理程序中
背景
无论您如何看待,管理开发都是一项艰巨的任务。随着您的开发团队和应用程序规模的增长,随着需求的不断变化,您的管理开销必然也会随之增加。为了维持稳定性,需要持续进行文档记录、回归测试和版本管理,同时又不失去“动力”。
随着不断发展的应用程序改变生成版本,有许多“应用程序主机”需要同步。这通常会导致大量不必要的错误,以及大量时间专门用于在各种测试/暂存和生产平台之间同步“最新生成”版本。
本文是旨在帮助您以最少的时间有效管理更改的众多文章之一。由于代码版本管理程序在我出生前就已经存在,本文将重点介绍数据库版本管理。
您的选择
CVS、SourceSafe、ClearCase、SourceGear、SubVersion……代码版本控制程序层出不穷……那数据库呢?我们如何以最小甚至零停机时间无缝升级生产数据库的模式/数据?多个开发人员如何“获取最新”的数据库模式和数据?
您应该知道,一些优秀的数据库“差异”程序已经存在。例如 StarInix、RedGate 和 SQL Delta。其中一些高端产品价格昂贵,因为它们会为您生成同步脚本。还记得您曾经信任一个通用的程序为您生成 HTML 的时候吗?您真的想信任一个通用的程序自动更改您的数据库模式吗?
别误会,这些程序是查看和解决差异的绝佳起点,但我强烈建议采取更“亲力亲为”的方法来制定版本管理策略。为什么?
当您自己编写脚本时,您不仅拥有更高的控制度,而且对数据库中发生的变化、何时发生以及谁发起了更改有着更高的意识。
提议的解决方案
由于每个数据库都带来了其独特的挑战,因此我不会提供一个一刀切的可下载应用程序。我提供一种经过时间考验且已被证明有效的方法,该方法足够灵活,可以适应您的开发周期的工作流程,前提是您愿意通过 SQL 脚本代码来动手实践!
您将需要什么
- 现有的源代码控制系统
- 任何一种都可以。您可以使用与代码库相同的存储库,或者在最后的手段下,只使用一个位于可访问网络共享上的文本文件。
- 运行强大脚本的数据库权限
- (被指定的开发人员需要诸如“DBOwner”之类的访问权限,以便他们能够进行更改。)
- 熟练的 SQL 知识
- 您需要知道如何编写脚本来更新您特定品牌数据库的模式和数据。90% 的时间,这些脚本都相对简单。
了解此方法的局限性
- 有些数据类型无法通过基于文本的脚本进行操作。这些包括
- 二进制 (image, bitmap, blob)
- 可变长度文本 (Text-16)
- 这可能会因您的数据库平台而异。您能否使用复制?
- 依赖于时间数据的更改(datetime、由种子自动生成的 ID)无法保证在多个数据库中保持一致。
- 如果您的代码依赖于数据库版本之间的一致自动编号,请确保这不是概念设计上的缺陷!
策略
版本表(本质上)
为了灵活性和详尽性,请在数据库中声明一个表(我称之为 DBVersion),用于作为版本指示器。每一行都应能存储某种形式的版本标识符(例如:如果您使用 FogBugz,这将是您的 CASEID)、简要描述、更改发生时间、发起更改者以及您认为方便记录的任何额外信息。 此表将消除确定数据库上次更新的猜测。 |
|
该脚本
结合该表,我们需要一个脚本来执行由表中的值驱动的操作。以下是脚本算法
Start transaction [ALL]
For each version change I to N Do:
If(not exists (select (1) from DBVersion where DBVersionID=I))
Begin
Start transaction [i]
<[do your update stuff here]>
If failure, rollback and exit
Else Insert into DBVersion values(I,<>,getDate(),User_ID())
Commit transaction [i]
End
Next [i]
Commit transaction [ALL]
现在,仔细观察。此脚本的设计目的是仅执行它需要执行的操作,而不会执行相同的更新两次。如果数据库已记录了某个版本更改,脚本将直接跳过。该脚本将在不同版本的数据库上运行,并产生一致的结果,并且有清晰的审计跟踪记录更改的发生方式和时间。如果脚本因故失败,嵌套事务结构将强制数据库回滚到执行之前的状态。
如果脚本以这种方式正确构建,那么同步数据库可能就像“获取”最新版本的代码并运行脚本进行测试一样简单。有人抱怨最新的生成版本不起作用?告诉他们再次运行脚本,然后让他们走开!
如果脚本有一个基本规则,那就是切勿从脚本中删除内容。有时,似乎很诱人删除脚本中过去的错误,但这会破坏 DBVersioning 表中更改的序列记录!与其删除,不如添加一个新的版本更改来修正之前的错误。毕竟,大多数脚本(希望)执行时间不会太长。
尽管 DBVersioning 方法不必从数据库创建之初就实现,但从头开始有一个生成脚本有明显的好处。
使用 SQL Server 的实际示例
每个重大项目都始于一步。采用这种版本控制并“养成习惯”是最难的部分,因此我将为我们钟爱的 SQL Server Northwind 数据库提供一些代码作为起始示例。
--Enforce this is Northwind
USE Northwind
--To ensure ACID, begin the main transaction
BEGIN TRANSACTION main
--make sure the DBVersion table exists
if(not exists (select (1) from dbo.sysobjects
where id = object_id(N'[dbo].[DBVersion]') and
OBJECTPROPERTY(id, N'IsUserTable') = 1))
begin
print N'Building the foundation ''DBVersion'' Table'
BEGIN TRANSACTION one
--build the table
CREATE TABLE DBVersion (
[DBVersionID] int NOT NULL ,
[Description] varchar (255) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
[ExecutionDate] datetime NOT NULL ,
[UserID] int NOT NULL
)
--any potential errors get reported,
--and the script is rolled back and terminated
if(@@error <> 0)
begin
ROLLBACK TRANSACTION
RETURN
end
--insert the first version marker
INSERT INTO DBVersion values (1,'Build the DBVersion Table',
getDate(),User_ID())
COMMIT TRANSACTION one
end
--finished step one
---------------------------------------------------------------
---------------------------------------------------------------
--Continuing, adding to this script is relatively simple! Observe...
if(not(exists(select (1) from DBVersion where DBVersionID = 2)))
begin
print N'Adding a ''DateCreated'' field onto the Customers table for auditing purposes'
BEGIN TRANSACTION two
--add the column, or whatever else you may need to do
ALTER TABLE Customers add DateCreated DateTime not null default getDate()
--any potential errors get reported, and the script is rolled back and terminated
if(@@error <> 0)
begin
ROLLBACK TRANSACTION
RETURN
end
--insert the second version marker
INSERT INTO DBVersion values (2,
'Adding the DateCreated field to customers',getDate(),User_ID())
COMMIT TRANSACTION two
end
--from here on, continually add steps in the previous manner as needed.
COMMIT TRANSACTION main
请注意,随着上述代码变得越来越冗长,这些脚本可能会在短时间内达到惊人的长度,因此将多个“章节”存储在数据库概念演变中可能对您有利。
修改
上面的脚本是一个极简的示例,供您根据自己的具体需求进行扩展。随意更改它,核心概念在于算法。
- 也许您可以编写一个存储过程,它将版本标识符和一些更新批处理代码作为参数。
- 也许您的部分数据将依赖于复制服务进行更新。
- 也许更新遗留数据库需要一些额外的工作。
由于每个项目都有其独特的挑战,请务必帮助开发社区,并分享您对该概念的经验和补充。感谢您的阅读,并祝您一切顺利!