考虑这样的场景,现在每天要同步更新的数据,为了记录哪些数据发生过变更,需要对表添加一个rowversion列来标识被更新过。恰好这个表比较大,而且有发布订阅。由于该字段对于订阅表来讲,无实际意义,而且添加一个有默认值的不可为空的8字节字段,会产生数据空间分配,或许带来大量的页拆分,对IO压力会加大,所以考虑将此表的发布项暂时改为不支持架构复制,等添加完该字段后,再将其改为支持架构复制。
下面来做这个测试:
1.下面是一个正常的事务发布,将DB_1中的T_1发布到DB_2的T_1表中。
2.修改发布项DBrepTEST关于架构复制的选项,设置为不支持
DECLARE @publication AS sysname
SET @publication = N'DBrepTEST'
USE DB_1
EXEC sp_changepublication
@publication = @publication,
@property = N'replicate_ddl',
@value = 0
GO
3.然后,添加一个字段,类型为rowversion
use DB_1
alter table t_1 add rv rowversion
4.经过观察,发布订阅一切正常,该字段并未被创建在订阅库DB_2上
5.我们在另外一个进程开始一个sql,向表DB_1.dbo.T_1 insert data.
while 1=1
begin
waitfor delay '00:00:01'
insert into DB_1.dbo.T_1(name)select 'a'
end
6.发现数据已同步到DB_2.dbo.T_1,但rv字段并未被同步,现在一切正常
7.现在将发布项DBrepTEST修改回支持架构复制
DECLARE @publication AS sysname
SET @publication = N'DBrepTEST'
USE DB_1
EXEC sp_changepublication
@publication = @publication,
@property = N'replicate_ddl',
@value = 1
GO
8.然后,此时向表DB_1.dbo.T_1新加一个int类型字段
use DB_1
go
alter table t_1 add age int
9.此时发布订阅出错
出错文字为:
Command attempted:
if @@trancount > 0 rollback tran
(Transaction sequence number: 0x000000580000013C000100000000, Command ID: 6)
Error messages:
A DDL change has been replicated. (Source: MSSQL_REPL, Error number: MSSQL_REPL27332)
Get help: http://help/MSSQL_REPL27332
列名 'rv' 无效。 (Source: MSSQLServer, Error number: 207)
Get help: http://help/207
10.由上面的信息,可以看出此时是由于在订阅库上找不到rv这个列造成的,为了确定这一点,我们看下这个事务执行的是什么
use distribution
go
EXEC sp_browsereplcmds '0x000000580000013C000100000000','0x000000580000013C000100000000'
11.由10的结果中的command列,得到如下需要应用到订阅库的t-sql
ALTER TABLE [dbo].[T_1] add age int
if object_id(N'[dbo].[sp_MSins_dboT_1]', 'P') > 0 drop proc [dbo].[sp_MSins_dboT_1]
--
if object_id(N'dbo.MSreplication_objects') is not null delete from dbo.MSreplication_objects where object_name = N'sp_MSins_dboT_1'
--
create procedure [dbo].[sp_MSins_dboT_1]
@c1 int,
@c2 varchar(20),
@c3 int
as
begin
insert into [dbo].[T_1](
[id],
[name],
[rv],
[age]
) values (
@c1,
@c2,
default,
@c3 )
end
--
if columnproperty(object_id(N'dbo.MSreplication_objects'), N'article', 'AllowsNull') is not null exec sp_executesql @statement = N'insert dbo.MSreplication_objects (object_name, publisher, publisher_db, publication, article, object_type) values (@object_name, @publisher, @publisher_db, @publication, @article, ''P'')', @parameters = N'@object_name sysname, @publisher sysname, @publisher_db sysname, @publication sysname, @article sysname', @object_n
ame = N'sp_MSins_dboT_1', @publisher = N'mySrv\SQL2008', @publisher_db = N'DB_1', @publication = N'DBrepTEST', @article = N'T_1'
--
--
if object_id(N'[dbo].[sp_MSdel_dboT_1]', 'P') > 0 drop proc [dbo].[sp_MSdel_dboT_1]
--
if object_id(N'dbo.MSreplication_objects') is not null delete from dbo.MSreplication_objects where object_name = N'sp_MSdel_dboT_1'
--
create procedure [dbo].[sp_MSdel_dboT_1]
@pkc1 int as begin
delete [dbo].[T_1] where [id] = @pkc1
if @@rowcount = 0 if @@microsoftversion>0x07320000 exec sp_MSreplraiserror 20598
end
--
if columnproperty(object_id(N'dbo.MSreplication_objects'), N'article', 'AllowsNull') is not null exec sp_executesql @statement = N'insert dbo.MSreplication_objects (object_name, publisher, publisher_db, publication, article, object_type) values (@object_name, @publisher, @publisher_db, @publication, @article, ''P'')', @parameters = N'@object_name sysname, @publisher sysname, @publisher_db sysname, @publication sysname, @article sysname', @object_n
ame = N'sp_MSdel_dboT_1', @publisher = N'mySrv\SQL2008', @publisher_db = N'DB_1', @publication = N'DBrepTEST', @article = N'T_1'
--
--
if object_id(N'[dbo].[sp_MSupd_dboT_1]', 'P') > 0 drop proc [dbo].[sp_MSupd_dboT_1]
--
if object_id(N'dbo.MSreplication_objects') is not null delete from dbo.MSreplication_objects where object_name = N'sp_MSupd_dboT_1'
--
create procedure [dbo].[sp_MSupd_dboT_1]
@c1 int = NULL,
@c2 varchar(20) = NULL,
@c3 int = NULL,
@pkc1 int = NULL,
@bitmap binary(1) as begin
update [dbo].[T_1] set
[name] = case substring(@bitmap,1,1) & 2 when 2 then @c2 else [name] end,
[age] = case substring(@bitmap,1,1) & 4 when 4 then @c3 else [age] end where [id] = @pkc1
if @@rowcount = 0 if @@microsoftversion>0x07320000 exec sp_MSreplraiserror 20598
end
--
if columnproperty(object_id(N'dbo.MSreplication_objects'), N'article', 'AllowsNull') is not null exec sp_executesql @statement = N'insert dbo.MSreplication_objects (object_name, publisher, publisher_db, publication, article, object_type) values (@object_name, @publisher, @publisher_db, @publication, @article, ''P'')', @parameters = N'@object_name sysname, @publisher sysname, @publisher_db sysname, @publication sysname, @article sysname', @object_n
ame = N'sp_MSupd_dboT_1', @publisher = N'mySrv\SQL2008', @publisher_db = N'DB_1', @publication = N'DBrepTEST', @article = N'T_1'
--
--
可以看到对于添加字段,会重新创建对表操作的三个proc,本例中,insert这个proc会将rv加入进来,并且值为default,如下所示,但由于该列并不存在于订阅中,所以就会报错了,由此造成分发中断,后续事务无法应用到订阅端。
create procedure [dbo].[sp_MSins_dboT_1]
@c1 int,
@c2 varchar(20),
@c3 int
as
begin
insert into [dbo].[T_1](
[id],
[name],
[rv],
[age]
) values (
@c1,
@c2,
default,
@c3 )
12.虽然可以通过在订阅端手工端建一个rv列,将其设置为可为null的varbinary(8)类型,但这个列对于订阅并没有实际意义。
use DB_2
go
alter table t_1 add rv varbinary(8)
总结,经过测试,发现当添加其它类型字段,并设置为有默认值且not null时,不会有此错误出现,目前发现仅是rowversion类型列,不知是否为SQL Server bug.
测试环境:windows server 2003 enterprise 32bit sql server 2008 sp2 standard 32bit
作者:nzperfect
出处:http://www.cnblogs.com/nzperfect/
引用或者转载本BLOG的文章请注明原作者和出处,并保留原文章中的版权信息。