Condividi tramite


错误20598 -在订阅服务器上找不到该行

 在事务复制中,有很多允许您进行复制配置环境自定义的情况,它们有可能会引发数据不同步的可能,从而来引发错误20598 (“The row was not found at the Subscriber“)。重新同步和初始化订阅方的数据是默认也是我们推荐的方式,但是它的开销通常较大而且会引发不希望遇到的结果。

 

如果您想要确保订阅服务器和发布服务器的数据已经完全同步到了某一个时间点上,重新同步和初始化订阅方通常是唯一的选择。如果您选择了任何其他的同步方式,那您需要为最终数据是否同步来负责,并确认数据不再是“不同步”的状态。

 

 错误20598(“找不到该行”)可能发生的原因

我们要检查是否遇到了以下问题:

1. 发布数据库启用了read_committed_snapshot隔离级别可能导致分发代理报20598错。我们可以通过检查发布数据库的隔离级别属性是否为read_committed_snapshot判断这种问题的可能性。

use <pubdb>

go

dbcc useroptions

go

2. 订阅方手动改变数据

3. 外键或触发器的“Not For Replication” (NFR)属性没有开启

 

如何重现问题

我们可以通过下面的步骤来重现错误20598:

1. 重现情景2 的错误20598 (订阅方手动改变数据)

1) 创建一个事务发布并同步到一个订阅方

2) 删除订阅方的某个行

3) 更新发布方的同一个行

4) 开始同步并查看复制监视器(Replication Monitor)中的相应20598错误

 

例:

- Insert into Publisher..Table_1 (userdata) values ('firstdata') (插入数据到发布方)

- Synchronize with Subscriber(与订阅方同步)

- Delete from Subscriber..Table_1 where userdata = 'firstdata'(从订阅方删除)

- Update Publisher..Table_1 set userdata = 'firstdat' where userdata = 'firstdata'(更新发布方的同一行数据)

- Synchronize with Subscriber(与订阅方同步)

- 在再次同步之后,会出现错误20598 – 通过复制监视器查看该错误信息

2. 重现情景3的错误20598 (外键或触发器的NFR属性没有开启)

1) 新建表T1和T2。插入一些测试数据。在T1上创建一个DELETE触发器(不开启NFR)来同时删除T2中的一行数据。

2) 创建事务发布来发布T1和T2。

3) 与订阅服务器同步。

4) 在发布方,删除T1中的一行。

5) 再次同步订阅方,并查看复制监视器中的错误20598

 

例:

- Delete from Publisher..T1 where id = 1(从发布方T1表中删除一行数据)

- 这一个DELETE操作在发布方实际上会做两个删除操作:

<1> 首先在T1中DELETE一行

<2> 然后在T2中DELETE一行(通过引发触发器)

- 与订阅方同步

- 在同步重试后,会出现错误20598 – 在复制监视器中查看该错误

- 这是由于在订阅方实际上有3个DELETE操作(一个DELETE在T1上,2个DELETE在T2上):T2上的第1个DELETE是从发布方复制到订阅方的DELETE (这是由发布端触发器触发的),第二个DELETE来自于订阅方T1表触发器的触发。因此,T2上的第二个DELETE会造成错误20598。而如果我们在订阅方T1触发器上开启NFR,当SQL发现在订阅方T1表上的DELETE操作是复制同步时的动作,该操作将不会在订阅方触发触发器来做T2表的第二个DELETE操作,从而可以保证在订阅方T2表上只有一个DELETE操作(来自发布方)执行。这样,错误20598就不会发生了。

 

 

故障排查

这个问题排查的难度在于,当错误20598出现时,说明发布和订阅两端的数据已经不一致了,错误20598只是帮助暴露了这个数据不一致的问题。但是至于为什么数据会不一致,什么时候发生的数据不一致,由于已经是之前出现的情况了,并没有特定的数据集或者很好的方法可用来显示该问题的原因。我们只能试着理解为什么那一行没有在订阅方出现,来获得数据不一致的原因。

 

我们需要思考下面的问题:

1) 您是如何被告知这个错误的?

2) 错误信息的内容是什么?

3) 订阅端的表会被用户修改吗?

4) 识别出在订阅方中没有被找到的行了吗?

 

当得到了上面问题的答案之后,可以通过很多方式继续尝试找出问题。基于我们的经验,这个问题的原因无外乎前面篇幅提到的几种。同时,我们需要意识到 - 您可能并不需要了解导致问题的原因才允许复制继续。在关键情况下,您可能只想要确定订阅方找不到该行的具体信息,然后恢复正常的复制。

 

前面提到,重新同步和初始化订阅方的数据是确保订阅服务器和发布服务器的数据已经完全同步到了某一个时间点上的唯一,也是我们推荐的方式,但是它的开销通常较大。因此,对于错误20598,我们可以考虑如下两种方式来快速使得复制继续 – 以期待后面的一些手动方法解决数据不一致的问题:

1) 插入在订阅方丢失的行

2) 使用发布代理的SkipErrors参数来忽略跳过错误20598

 

注意:如果您选择了其他手动的同步方式,那您需要为最终数据是否同步来负责,并确认数据不再是“不同步”的状态。

 

1. 找到丢失的行

我们可以通过下面的步骤找到订阅方丢失的行:

- 在复制监视器中检查错误20598

- 通过查询MSDistribution_History表来检查该错误

 

1. 从复制监视器中得到的关键数据是事务序列号(Transaction sequence number)和命令ID(Command ID)。复制监视器的信息类似如下:

Command attempted:

if @@trancount > 0 rollback tran
(Transaction sequence number: 0x0000002500000167000400000000, Command ID: 1)

 

2. 将获取的事务序列号作为参数传递到sp_browserreplcmds - 可以去掉最后一个非零数之后的所有尾随零,比如:

 

exec distribution..sp_browsereplcmds '0x00000025000001670004', '0x00000025000001670004'

 

运行结果可能包含多行。尝试匹配command_id列和错误信息中给出的ID。当找到了正确的command_id,您将会注意到有一个“命令(command)”列,包含了正被复制执行的实际命令。如果不能获得失败的命令,可能需要查看输出中的其他行,直到找到了引用订阅方不存在行的那一个命令及语句。下面是sp_browserreplcmds中的命令范例:

 

{CALL [sp_MSupd_dboTable_1] (,N'firstdat ',,1,0x02)}

 

这说明我们正在运行Table_1的更新操作,其中主键值为“1”。这意味着我们的丢失行是主键为1(Primary Key =1 )的数据。字串N’ firstdat ’是正在被修改的值。

 

2. 后续操作及更多操作

一旦我们找到了丢失的行,有很多方法可以知道它为什么没在订阅方。有时候最快的方式是查看数据细节,这可以给您一些关于它来源或移除的提示。一些要查看的细节为:

1)行内包含日期的输入或修改值吗?

2)行中包含进行插入或更改操作的用户的用户名吗?

3)用户可以在订阅方的表上修改该数据吗?

4)行属于任何特殊群组吗?是属于某个生产商或供应商的销售记录或库存记录吗?

等等

 

如果上面的问题/答案没有得出数据不在订阅方的原因,我们可以开始查看环境来排查数据不在订阅方的可能原因。有时候最简单的方式是测试丢失行的行为。一些测试情景包括:

-在订阅方插入丢失行并允许复制继续-现在有没有成功复制了修改?

-删除发布方的行-有没有成功复制了删除?

-在发布方重新插入行-有没有成功复制插入?

 

如果上面的情景过程中出现了其他错误信息,那么就在失败的步骤上进行故障排除。

 

3.要收集的数据

如果以上测试不能解释某些行不出现在订阅端的原因,那么我们需要收集复制环境的相关数据。首先,考虑收集下面的信息:

1) 一个完整的发布脚本

2) 失败代理的verbose输出文件

3) 产生发布方和订阅方相关发布表的创建脚本

4) 外键列表及引用这些发布表的触发器脚本。我们可以使用sp_help(不包含触发器)或直接查询sys.objects来获得相关的信息,比如:

select object_name(parent_object_id),* from AdventureWorks.sys.objects where type in ('PK','F','TR')

5) 复制在订阅方为各订阅表创建的插入、更新、删除操作存储过程的脚本。它们通常命名为:sp_MSupd_dboTable_1,sp_MSins_dboTable_1,sp_MSdel_dboTable_1

6) 分发过程失败时,订阅方的事件探查器跟踪文件

 

然后从上面收集的数据中检查下面的信息:

1) 检查发布脚本,查看是否有任何发布表过滤器来控制复制到订阅方的数据。可以通过检查sp_addarticle的@filter_clause参数来确认。数据有可能因为不满足过滤语句中的条件而不被复制。

2) 检查verbose输出文件来确保您已经识别出了正确的事务序列号及命令ID。

3) 检查创建表的脚本文件,确定表结构、定义在发布方和订阅方是一样的。

4) 检查表上的参照完整性对象,判断变化是否被外键或触发器影响。

5) 检查存储过程脚本,确定它们是否被定制或更改而导致了不正常的工作。

6) 检查事件探查器跟踪文件来确定其他发现。可能在跟踪文件中出现的例子为:

- 证实某数据正在被修改

- 由复制活动、操作引发的触发器

- 其他在复制监视器中不可见的错误

 

有时,除了上面的条目,可能还需要在订阅数据库的相关表上创建修改监测触发器(audit triggers)来确定用户是否在人为修改订阅端表的数据。关于这个部分,此处不再详表。如果有这方面的需求,请联系我们,微软技术支持,我们将竭诚为您解惑。