有两种状况会形成更新丢失,第一种是不正确的设置,例如外键或触发器的“Not For Replication” (NFR)属性没有开启。详情请参考http://blogs.msdn.com/b/apgcdsd/archive/2012/01/10/10254809.aspxsql
第二种是产品bug,例如使用了 MaxCmdsInTran http://support.microsoft.com/kb/2648158数据库
前一阵我在作case的时候遇到了一个新的bug。这个bug在sql server 2005,2008, 2008R2这些版本中均可以重现,而且目前没有推出相应的fix. 须要注意的是,sql server 2012中相关的行为已经从新进行了设计,不会存在这个问题。spa
描述 设计
=== 日志
环境:事务复制的订阅不是经过快照进行的初始化 server
条件:Logreader没有运行。 blog
操做:咱们向publicaiton添加了一个新的Article. token
此时在log reader中止期间到完成添加article以前,publication的article出现了更新/删除/插入,那么这些变动都不会传递到订阅。 假设 Logreader在11:00中止,咱们在12:00添加了一个新的artilce(假设瞬间完成),13:00从新启动Logreader 。那么11:00~12:00之间的更新将所有丢失。 进程
缘由 事务
===
当publicationdatabase的article发生更新时, 会产生相应的日志,Log reader会读取这些日志信息,将他们写入到Distribution 数据库的msrepl_transactions和msrepl_commands中。具体的技术细节我会在之后的文章里介绍。
Msrepl_transactions中的每一条记录都有一个惟一标识xact_seqno,xact_seqno对应日志中的LSN。 因此能够经过xact_seqno推断出他们在publicationdatabase中的生成顺序,编号大的生成时间就晚,编号小的生成时间就早。
Distributionagent包含两个进程,reader和writer。 Reader负责从Distribution 数据库中读取数据,Writer负责将reader读取的数据写入到订阅数据库.
reader是经过sp_MSget_repl_commands来读取Distribution数据库中(读取Msrepl_transactions表和Msrepl_Commands表)的数据
下面是sp_MSget_repl_commands的参数定义
CREATE PROCEDURE sys.sp_MSget_repl_commands
(
@agent_id int,
@last_xact_seqno varbinary(16),
@get_count tinyint = 0, -- 0 = no count, 1 = cmd and tran (legacy), 2 = cmd only
@compatibility_level int = 7000000,
@subdb_version int = 0,
@read_query_size int = -1
)
这个存储过程有6个参数,在Transactionalreplication 中,只会使用前4个(而且第三个参数和第四个参数的值是固定不变的.分别为0和10000000)。下面是一个例子:
execsp_MSget_repl_commands 46,0x0010630F000002A900EA00000000,0,10000000
@agent_id表示Distributionagentid,每一个订阅都会有一个单独的Distributionagent来处理数据。 带入@agent_id后,就能够找到订阅对应的publication 和全部的article。
@last_xact_seqno 表示上一次传递到订阅的LSN。
大体逻辑是:Reader读取分发数据库中LSN大于@last_xact_seqno的数据。 Writer将读取到的数据写入订阅,并更新相应的LSN.(subscription数据库的 MSreplication_subscriptions表的 transaction_timestamp列和Distribution数据库的msDistribution_history表的xact_seqno列)。而后Reader会继续用新的LSN来读取后续的数据,再传递给Writer,如此往复。
假设如今订阅段的数据已经更新到了0x0010630F000002A900EA00000000, 以后咱们认为地向Msrepl_transactions表和Msrepl_Commands表插入了一批数据,xact_seqno对为0x0010630E000002A900EA00000000. 虽然这些数据的格式所有有效, 但Distributionagent是不会读取这些新加入的数据的,由于他们"太旧了"(他们的xact_seqno小于订阅的xact_senqo).
当Log reader中止时, 咱们是能够添加article的。 而且相应的操做也会向msrepl_transactions和msrepl_commands插入数据,这些数据并非由Log reader传递的,而是经过linkedserver直接向Distributor直接写入数据。 Distributionagent会读取这些数据,并更新相应的xact_seqno。 而"Log reader中止"到"添加新article"这段期间发布产生的数据的xact_seqno是小于"添加新article"的xact_seqno,因此这些跟新会丢失。 提及来比较抽象,下面举个例子。
10:00到11:00期间publication database共生成了三条事务,对应的xact_seqno分别为
0x0010630F000006B9001E
0x0010630F000006F10004
0x0010630F000006F20004
11:01将log reader中止
11:01~12:00期间publication database共生成了4条事务
0x0010630F000006F30004
0x0010630F000007080005
0x0010630F000007D40205
0x0010630F0000098C005C
但因为log reader没有启动,因此msrepl_transactions表内依然是三条数据. 12:01完成添加article的操做,msrepl_transactions内生成相应的记录0x00106310000000100100。
此时msrepl_transations内共有四条记录
此时订阅端的xact_seqno为0x0010630F000006F20004, distribution agent将读取大于0x0010630F000006F20004的数据, 只有0x00106310000000100100符合要求。 订阅段完成的全部的数据同后,相应的xact_seqno为0x00106310000000100100
此时开启log reader, 累计的数据传递完毕后,msrepl_transactions共有8条记录,但distribution agent不会再去读取以前的数据了…
影响
===
在有些极端状况下,即便您认为Log reader处于运行状态,仍是会出现跟新丢失的状况。缘由在有些状况下,Log reader成为了deadlock的牺牲者被kill掉,但Log readeragentjob有自动retry的机制,会在一段时间后自动恢复, 因此您可能没法察觉。 是否出现死锁,您查询MSLog reader_history,MSrepl_errors会找到相似的"N'Transaction (Process ID 400) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.',NULL,1,N' SQL Log Reader Agent encountered an error. Publisher: xxx Publisher Database: xxx".
解决办法
===
将Distributor升级至sql servr 2012
若是您暂时没有办法升级,能够采用如下两种方法:
更多(2013.10.23补充)
===
若是添加一个非快照初始化的订阅时,该发布对应的发布数据库的全部已存在订阅也会出现更新丢失的状况,不管这些都订阅经过何种方式初始化,或属于那个发布.
由于添加订阅的操做也会经过linked server向distribution database内写入数据
解决方法和以前相同
该问题已经在sql server 2008 r2 sp2 cu13中解决(2014.07.01补充)