《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
2、解析commands
根据上面查询的结果,取出xact_seqno(出错的命令的事务号)、command_id(命令id),在根据下面的系统存储过程定位到具体的语句
USE distribution go sp_browsereplcmds ' 0x00026BBC000A3DDE000400000000 ' , ' 0x00026BBC000A3DDE000400000000 ' -- 两个字符串均是上一步获取的xact_seqno
在结果集中使用上一步的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已填补 )
对于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列的值;
最后再次强调,修改订阅端存储过程存在风险,请谨慎操作~