Replication的犄角旮旯(二)--寻找订阅端丢失的记录

 

 

《Replication的犄角旮旯》系列导读

Replication的犄角旮旯(一)--变更订阅端表名的应用场景

Replication的犄角旮旯(二)--寻找订阅端丢失的记录

Replication的犄角旮旯(三)--聊聊@bitmap

Replication的犄角旮旯(四)--关于事务复制的监控

Replication的犄角旮旯(五)--关于复制identity列

Replication的犄角旮旯(六)-- 一个DDL引发的血案(上)(如何近似估算DDL操作进度)

Replication的犄角旮旯(七)-- 一个DDL引发的血案(下)(聊聊logreader的延迟)

Replication的犄角旮旯(八)-- 订阅与发布异构的问题

Replication的犄角旮旯(九)-- sp_setsubscriptionxactseqno,赋予订阅活力的工具

---------------------------------------华丽丽的分割线--------------------------------------------

 

接触Replication时间长了,遇到“应用复制的命令时在订阅服务器上找不到该行。”这样错误的几率大大增加,而如何定位并手动填补数据成了DBA的必修课;本文将介绍一种暴力方法来追踪已丢失的热点数据,尤其是对于同表多条记录丢失的问题,提高DBA的工作效率;

本文设计思路由陈璟童鞋提供,本人只是加以整理,如有侵权,烤鸭伺候……

本方法虽多次经受验证无误,但多次被MS supporter们建议不要尝试使用此方法,还望各位DBA三思!

一般来说,定位“订阅端丢失的记录”分成以下几步:

1、通过xact_seqno、command_id定位到具体命令

2、解析commands,确定命令类型(insert、update、delete)、对象名称、主键

3、根据上述获取的条件补数(insert或DTS),这是我们的关键,也是我们需要简化的步骤

关于定位失败的命令,可以参考微软官方博客

http://blogs.msdn.com/b/apgcdsd/archive/2012/01/10/10254809.aspx

 

没错,我也是这样操作,但如果你发现,刚刚补过一条记录后,msrepl_errors又出现新的记录,咋办?再1、2、3的执行一遍?关键的问题是我们也不知道到底丢失了多少命令。如果这是发生在夜里,几分钟报一次警,持续1、2个小时,相信所有的DBA们都会疯掉……so,自己动手丰衣足食吧;

 

先来分析一下可能造成“找不到行”的复制命令的类型;

1、insert

  这类操作对DBA绝对是个blackhole;试想一下,如果一个insert操作丢失了,如果这个丢失的记录后续没有通过复制进行过update、delete,你是绝对发现不了的;没办法,这样的工作只能交给验证订阅或者定期进行tablediff这类第三方工具搞定了,不过我相信大部分DBA都是在业务方发现数据不一致以后才后知后觉的……

2、update

  update是三个DML操作里面比较复杂的,一个update命令传到订阅端但发现没有这条记录的时候就会报错,由于在发现命令丢失时发布端已经完成更新,所以直接手动从发布库里导入这条记录到订阅端即可;

3、delete

  delete是最简单的无需关心的操作,如果一个delete的复制命令传到订阅端发现没有记录,你会像处理update那样重新从发布库导入这条记录到订阅端?那你一定是大脑掉线了……帅锅,这时候发布库已经没有这条记录了,然后你会疯了一样的问自己肿木办,肿木办么?

  有人说,在订阅端insert一条只有主键的伪记录,然后delete就可以正常下去了。没错,这确实是个办法,但不是个好办法,毕竟一个insert你也是要敲上十几个甚至几十个字符的……其实处理方法很简单,已经删了的记录就没必要再找回来了,关掉监控就行了;当然我指的是MS errors的报警监控。

 

处理方法:

1、定位具体命令

  你还在通过复制监视器查看出错信息?那补上一条数估计要几分钟(等待出错信息刷新的时间),要是丢了几十条记录,那你这一天就不用干别的事情了;

  直接从distribution.dbo.msrepl_errors里查吧;  

SELECT * FROM distribution.dbo.MSrepl_errors
ORDER BY time DESC
View Code

2、解析commands

  根据上面查询的结果,取出xact_seqno(出错的命令的事务号)、command_id(命令id),在根据下面的系统存储过程定位到具体的语句

USE distribution
go
sp_browsereplcmds '0x00026BBC000A3DDE000400000000','0x00026BBC000A3DDE000400000000' --两个字符串均是上一步获取的xact_seqno
View Code

   在结果集中使用上一步的command_id定位到具体的行,取出command,就是出错的命令

3、分析命令

  [sp_MSupd_dbotest4]  这是调用订阅端的存储过程名,upd说明是update操作,test4是订阅端的对象名;

  ‘abc’  这个是update操作的value,具体对应的哪一个column,那就数数逗号吧(自己测试一下就会发现规律);实际上我们并不需要知道要更新哪一列;

  10002   这个是主键的value,复制命令到订阅端执行都是按照主键去操作的,这个看一下订阅端的存储过程就清楚了;

  0x02   这个是8进制的bitmap,简单说就是这一类操作的位图值,在这一章不会用到这个,后续的文章里会涉及到;

  至此,distribution的任务完成了,下面就是本文的关键——修改订阅端存储过程

4、修改订阅端存储过程

  到订阅数据库里找到对应的存储过程“sp_MSupd_dbotest4”,并生成脚本;

  @pkc1  这个就是test4的主键值;看到了吧,复制命令到订阅端都是按照主键操作的,即便你在发布端传入的是update table set a='abc'这样的全表操作;

  而下面的两个if + 一个exec sp_MSreplraiserror 20598,就是判断当更新数量为0时(@@rowcount=0)报一个20598的错误;

  改造原则:鉴于可能出现同一个表中多条记录丢失,我们可以先记录那些丢失记录的主键,然后批量的根据主键值去一次性导入到订阅端,这才是简化的关键;

  创建log表;

CREATE TABLE monitor.dbo.tmp_byxl_ReplLostlog
    (
      id INT IDENTITY  NOT NULL PRIMARY KEY ,    --记录序列号
      tbname VARCHAR(50) ,                    --表名
      t_type VARCHAR(10) ,                    --类型
      pkey VARCHAR(100) ,                    --主键名称及键值
      createdate DATETIME DEFAULT GETDATE() ,    --创建时间
      yn TINYINT DEFAULT 0                    --是否手动填补;0未填补,1已填补
    )
View Code

  对于update命令,我们需要的信息包括表名(test4)、操作类型(U)、主键名及键值(id=@pkc1);参照下图,在存储过程中的相应位置添加insert语句,同时注释掉报警语句;

  

   再查询一下记录表,我们要的信息就都在这里了。同时,由于关闭了报警,分发代理在下一次重试后可以正常继续执行下面复制命令,如果遇到多个记录丢失的情况,只要去记录表中查询即可;

  

  对于delete命令,正如之前所说,已经删除的命令就没有必要再找回了,打算留一个日志的童鞋可以参照update的处理方法,修改订阅端对应的del存储过程,insert到记录表中,或者干脆直接注释掉报警语句,忽略掉delete操作即可;

5、手动补数

  根据记录表中的记录,可以查看截止到当前时间点,之前所有的丢失记录情况,拼一下sql,用DTS就可以完成批量导入;

 

注意:

  1、此方法不建议长期使用,建议手动补数后注释掉insert语句,并打开报警语句;

  2、手动补数后,请将记录表中已操作的记录set yn=1,作为标记,以免重复insert时主键冲突;

  3、对于联合主键,存储过程中默认以@pkc1~@pkcn表示,请注意记录表中pkey字段的长度,以免溢出;

  4、示例中仅列出了int型主键,对于varchar型主键,请自行调整insert语句中pkey列的值;

 

最后再次强调,修改订阅端存储过程存在风险,请谨慎操作~

 

  

posted @ 2014-01-16 13:37  我是大菠萝  Views(1132)  Comments(2Edit  收藏  举报