解析“Not for Replication”

“Not for Replication”是在SQL Server复制中可应用于各个对象(例如Check约束、外键约束、触发器、标识列-Identity Column等)的属性。当数据库管理员想要让事务的行为在数据修改变更来自复制代理与来自一般用户事务的两种情况下不一样时,使用该特性。

范例说明

某公司在全国各地有多个销售点,每个销售点从终端用户得到订单并将请求复制到总公司的产品与派发部门。

总公司产品与派发部门的服务器作为发布方,各销售点作为订阅方,搭建成合并复制架构。每个销售点存有产品当前库存级别的信息;当主库商品售罄后,销售点将不再接受新订单。

复制拓扑中的表如下:Products为父表,与Orders表以Product_id列通过主键-外键约束进行连接。Product表还包括名为Stock_Available的列,用于跟踪产品的当前库存情况。

Products表:

Orders表:

除此之外,在Orders表上还有一个AFTER INSERT触发器(名为“Update_Stock”),用来检查对应于某product_id的商品有没有足够的库存。如果有,它会更新Products表,从当前库存中减去数量。

触发器示例:

create TRIGGER [dbo].[Update_Stock]

   ON [dbo].[Orders]

   AFTER INSERT

AS

BEGIN

declare @quantity as int

declare @product_id as int

declare @stock_available as int

select @product_id= product_id , @quantity = quantity from Inserted

select @stock_available= stock_available from Products where product_id=@product_id

if (@Quantity > @stock_available)

    rollback

else

    update Products set stock_available= stock_available- @quantity where product_id=@product_id

END

现在我们假设,在某个订阅服务器(这里称它为“订阅方#1”)上,有产品”Baseball Bats”(Product_id=88)的一个新的订单。在订阅方#1插入数据后,显示如下:

Orders表:

这个插入(对应第三行)操作将会引发插入触发器,使得Products表中,对应Product_id=88的Stock_Available(现有库存)列上的值减20。

Products表:

当该事务在订阅方#1完成后,订阅方#1与发布方同步。现在复制检测到,在订阅方#1曾经有两个事务需要更新到主发布服务器上:

1. 在Orders表上的插入操作

2. 在Products表上Stock_Available列的更新操作。(复制不管更新是否是来自触发器,对于复制来说,这只是Products表上的一个一般的更新事务)

在同步完成后,订阅方#1上的数据与主发布方的数据合并,两方的表看起来应该是一样的。但是,看下面在第一次同步完成后,从发布方得到的数据:

Orders表: 

    

 

Products表:

发现了吧,问题出现了!发布方的Stock_Available列应该为80而不是60。

这是为什么呢?

Products是父表,Orders是子表,默认情况下合并复制的同步顺序会先为父表复制事务然后再是子表。正如前面所说的,插入触发器所做的更新也被视为一般用户事务。Products表作为父表,更新事务首先到发布方将Stock_Available列设置为80。接下来Orders表上的插入操作也被应用到发布方,同时触发表上的AFTER INSERT触发器,这个触发器回到Products表并重新更新Stock_Available列的值,将Quantity值变成了60(80-20=60)。

也就是说,如果在第一次同步之后,而没有进一步变化之前,再次进行一次复制同步,发布方和订阅方的Stock_Available列将会有同一个错误值(例子中的60)。

如何避免这种情况?

这里就引出了“Not for Replication”属性存在的意义。我们的目的是:如果INSERT来自复制合并代理,它就不需要引发触发器;如果INSERT是来自一般用户操作,触发器应被触发来更新当前库存数据。我们可以通过在所有复制相关的服务器和数据库上设置并启用触发器的“Not for Replication”属性来实现。

我们需要更改触发器定义如下:

ALTER TRIGGER [dbo].[Update_Stock] 

   ON [dbo].[Orders]

   AFTER INSERT

NOT for Replication -- ‘marked as not for replication’

附加信息

同样,“Not for Replication”属性也适用于发布方和订阅方的Check约束、外键约束和标识列-Identity Column上。比如,从发布方复制数据到订阅方时,设置订阅服务器上的Check约束为“Not for Replication”,来防止复制代理在复制数据时由于此约束而被限制。

更多关于NFR(Not for Replication)的信息,参见下面的链接:

Controlling Constraints, Identities, and Triggers with NOT FOR REPLICATION
https://msdn.microsoft.com/en-us/library/ms152529.aspx