65.9K
CodeProject 正在变化。 阅读更多。
Home

SQL 2000 环境中合并复制的陷阱

2007年6月12日

6分钟阅读

viewsIcon

37522

本文介绍了在 SQL 2000 中进行合并复制时遇到的陷阱。

引言

本文将介绍在使用 Microsoft SQL 2000 数据库服务器的合并复制进行系统分离规划时,近期发现的一些问题。这些问题(或陷阱)是我工作所在公司首次在实际环境中遇到的,当时我们正在准备使用合并复制进行全面的系统分离,并进行我们内部软件的月度发布。本文提供的解决方案已经过测试,并且已知有效。

背景

我所在的公司为了跟上公司的持续增长和不断扩大的客户群,认为提高我们的网络形象和内部应用程序性能的最佳方法是进行彻底的系统分离,将我们的数据库服务器异地托管,减少带宽使用量,并提高我们网站的整体性能。我们决定将所有 Web 数据异地转移,并使用 SQL 2000 中的合并复制来保持内部数据和 Web 数据同步,因为内部应用程序也需要使用 Web 数据。

数据库以及数据库中的必需对象(存储过程、表、用户定义函数、触发器等)都被标记为复制,一切进展顺利。当我们创建数据库脚本时,无论是触发器、alter table 脚本还是存储过程,我们都会使用一个内部创建的版本控制头来防止旧脚本被提升到生产环境。在头模板中,脚本会在 SysObjects 系统表中搜索同名的存储过程。如果找到该存储过程,则将其删除然后重新创建,增加脚本的版本号,否则直接创建存储过程。这时就发现了第一个陷阱。如果一个存储过程已被合并复制标记为复制,则不能简单地删除并重新创建它,而必须执行搜索(对我们来说是正常的)。如果找到该存储过程,则脚本会修改该存储过程;如果找不到,则会创建一个(一个带有参数列表的空存储过程),然后修改它以添加存储过程的“核心内容”。这是一个足够简单的问题和解决方案,导致了一个小小的挫折,但我们能够及时恢复。

下一个陷阱是由我在进行月度内部应用程序发布和构建时发现的,这个陷阱不像第一个那样容易修复,并且如果未在系统分离之前发现,可能会造成巨大的问题。如果发生了这种情况,在分离后,当执行内部应用程序的任何月度构建时遇到这种情况(我们稍后会提到“情况”或事件序列),将会给我的公司带来严重问题。

场景

在为我的应用程序准备下一个月度发布的更新时,我创建并运行了一个 alter table 脚本,因为我需要为表添加一些额外的列。我在开发服务器上创建并运行了这个脚本,开发服务器是生产环境的镜像,或者我当时是这么认为的,它运行得很好,没有任何问题,所以我继续进行月度发布的任务。直到将当前月度发布提升到 QA 部门时,才发现了这个陷阱。在我运行我创建的一个 Alter Table 脚本时,出现了以下错误:

Version: 01.00 of: PAT dbo.*Alter_Table_Script.pat  
	(1 row(s) affected)  Server: Msg 4931, Level 16, State 1, 
Line 3 Cannot add columns to table *'Table_Name' because it is being 
	published for merge replication. - 
Aborting!!!

好吧,你可以想象当我看到这个时,我就感觉到情况不妙。当时是晚上九点,我知道在第二天 DBA 来上班之前我无能为力。

第二天早上

第二天早上七点,我已经和 DBA 一起在公司上班,试图找出哪里出了问题,以及这对我们的系统分离项目有什么影响。经过一番研究,他认为系统分离项目可能要泡汤了,除非我们能尽快升级到 SQL 2005,因为分离项目只剩几周时间了。办公室里大家都在拼命地搜索,Google 都快被用爆了,在线 SQL 2000 参考网站、内部 SQL 库(实际上非常丰富),我们三个人都在拼命寻找解决我们问题的方法。

第一个解决方案被证明并不可靠。当数据库被标记为复制时,后续的表也会被标记,SQL 会创建一个对应的表,表名是 `conflict_DBName_TableName`,所以我开始基于此构建查询,直到我们意识到 SQL 2000 不会清理自己,当一个表不再被标记为合并复制时,该表不会被删除,从而在运行查询时有可能出现“假阳性”。所以,这个解决方案当然被放弃了。这时,那个急于寻找解决方案的人找到了终极解决方案。

解决方案

当数据库被标记为合并复制时,SQL 2000 会创建一个新数据库,名为 `distribution`,在这个数据库中有一个名为“`MSArticles`”的表。这个表存储了所有被标记为合并复制的对象的名称以及对象类型。还有一个系统存储过程叫做 `sp_repladdcolumn`,但这个存储过程只能在已标记为复制的表上运行,否则会失败。这给了我需要的东西。我需要做的是,首先检查数据库是否已被标记为合并复制,因为如果不是,搜索 `distribution.dbo.MSArticles` 会导致错误,从而导致全部失败。考虑到这一切,我写了下面的脚本,它完成了我需要的所有工作,测试后将其发布给了团队中的其他开发人员。

* 表示表名和脚本名已更改。

Using the Code

我下面想到的脚本会缺少一些信息,这些信息是我们版本控制头特有的,并且是我所在公司的专有信息。但是,我会展示实际的脚本部分。

  • 检查数据库是否已被标记为合并复制
  • 检查表是否存在(只是一个我喜欢的双重检查,我不喜欢意外)
  • 检查表是否已被标记为合并复制
  • 相应地修改表
//
// Below is the script used to check required elements 
// so that an Alter Table script can be created and ran on a SQL 2000 database
// that has been marked for Merge Replication.
//
/*First check if the database has been marked for replication.
  If it hasn't and you search distribution.dbo.MSarticles 
  you will an "Invalid Object Name" error
*/
IF EXISTS(SELECT * FROM master..sysdatabases WHERE name='distribution')
    BEGIN
        /* Next make sure table exists in the database*/
        IF EXISTS (SELECT * FROM sysobjects WHERE type = 'U' AND name = 'Table_Name')
            BEGIN
                /* Now check if the table has been marked for replication*/
                IF EXISTS (SELECT Name, CASE(xtype) WHEN 'U' _
		THEN 'User table' ELSE xtype END AS ObjectType FROM sysobjects  
                WHERE NAME IN (SELECT source_object from distribution.dbo.MSarticles) _
		AND Name = 'Table_Name')
                    BEGIN    /*Table is marked for replication*/
                        /*
                         TODO: Process for adding a new column
                         Repeat as necessary for all columns to be added 
                        Exec sp_repladdcolumn @source_object = _
			N'[dbo].[Table_Name]', @column = N'column_name', 
                        @typetext = N'char(5) null', @publication_to_add = N'all'
                        */
                        Exec sp_repladdcolumn @source_object = _
			N'[dbo].[Table_Name]', @column = N'column_name', 
                        @typetext = N'TIMESTAMP NOT NULL', @publication_to_add = N'all'
                    END
                ELSE
                /* Table isn't marked for replication so alter as normal*/
                    BEGIN
                        ALTER TABLE
                            Table_Name
                            /* Add your columns here*/
                        ADD
                            /* Repeat as necessary for the columns you need*/
                            Column_Name Data_Type
                    END
            END
    END
ELSE
    /* The table is in a database that hasn't been marked for Merge Replication. 
       This check needs to be done, if you query the distribution.dbo.MSArticles 
       table in a database that hasn't been marked for Merge Replication 
       the whole query will fail as that database & table don't exist.
    BEGIN
        ALTER TABLE
            Table_Name
            /* Add your columns here*/
        ADD
            /* Repeat as necessary for the columns you need*/
            Column_Name Data_Type
    END

这就是我们现在用于 alter table 脚本的脚本,因为我们的许多数据库和数据库对象已被标记为合并复制。至于其他对象,例如存储过程,我们以前的脚本工作方式是简单地查询 `sysobjects` 来查看存储过程是否存在,如果存在则删除并重新创建(并且带有我们的版本控制头,版本会递增,防止旧版本被提升到生产环境,但这又是另一篇文章了)。所以我们团队为我们的存储过程设计了如下脚本:

/* First check sysobjects to see if the procedure exists, 
   if it doesn't exist then we need to create a shell of the procedure. 
   Use Dynamic SQL as the Create Procedure is supposed to be the first
   line in a stored procedure, so you cant do the norm within an IF block*/
IF NOT EXISTS (SELECT * FROM sysobjects WHERE type = 'P' _
	AND name = 'Stored_Procedure_Name')
BEGIN
    PRINT 'Creating Procedure Stored_Procedure_Name'
    DECLARE @SQL varchar(8000)
    SET @SQL = 'CREATE PROCEDURE Stored_Procedure_Name    /* Param List */ 
    AS'
    EXEC (@SQL)
END
PRINT 'Altering Procedure Stored_Procedure_Name'
GO
/* Then ALTER the procedure like you would alter any normal stored procedure*/
ALTER PROCEDURE Stored_Procedure_Name
    /* Param List */
AS

就是这样。这就是如何在 SQL 2000 数据库的合并复制中克服发现的陷阱。

关注点

这个场景在 SQL 2005 中得到了修正,并且我认为在后续的数据库程序版本中也会如此,所以如果你没有运行 SQL 2000,那么这应该不是一个问题。然而,如果你正在运行 SQL 2000 并计划进行合并复制,那么这些信息需要引起你 DBA 的注意。他很可能已经知道了,但如果他不知道,那么你将为他和你公司省去很多麻烦。

历史

  • 2007年7月12日:初次发帖
© . All rights reserved.