博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
使用SQLServer同义词和SQL邮件,解决发布订阅中订阅库丢失数据的问题
阅读量:5900 次
发布时间:2019-06-19

本文共 7745 字,大约阅读时间需要 25 分钟。

最近给客户做了基于SQLServer的发布订阅的“读写分离”功能,但是某些表数据很大,经常发生某几条数据丢失的问题,导致订阅无法继续进行。但是每次发现问题重新做一次发布订阅又非常消耗时间,所以还得根据“复制监视器”的提示,找到丢失的数据,手工处理。

定位缺失数据

首先,找到出问题的同步语句,在发布服务器的“复制监视器”上事务订阅的详细信息里面,找到出错的信息

尝试的命令:if @@trancount > 0 rollback tran(事务序列号: 0x0000992600000D09007F00000000,命令 ID: 19)错误消息:应用复制的命令时在订阅服务器上找不到该行。 (源: MSSQLServer,错误号: 20598)获取帮助: http://help/20598应用复制的命令时在订阅服务器上找不到该行。 (源: MSSQLServer,错误号: 20598)

 

然后在分发服务器上执行下面的SQL语句,

use distributiongosp_browsereplcmds '0x0000992600000D09007F00000000' ,'0x0000992600000D09007F00000000'go

根据命令ID(如上面的ID:19),找到具体的同步命令(Command列),类似于这样的:

{CALL [dbo].[sp_MSdel_dboT_TODO] ('697e7cacf5354a36be1ae4cf50dcdaa6')}

这里是 订阅库上的 sp_MSdel_dboT_TODO 存储过程,查看存储过程定义知道参数是ID的值,这里说找不到要删除的数据,那么我们在订阅库里面模拟增加这个ID的记录即可。添加数据,

补录数据

网上提供的解决方案是用一个工具生成差异的SQL数据然后给订阅库执行,但看了下觉得不是很方便,想起来SqlServer还提供一个 insert...from....语句,那么是否可以直接从发布数据库查询数据然后插入给订阅数据库呢?

可以使用同义词从发布库查询过来插入到本地订阅库,请看下面具体过程:

先在订阅库上建立一个同义词,比如下面为表 Biz_Customer 建立一个同义词 Biz_Customer_Master,建立的时候,要求指定同义词所在的服务器名称,数据库名称,架构,表名称等信息。

但是此时同义词还不能直接使用,还需要建立“链接服务器”,具体过程如下:

EXEC sp_addlinkedserver @server='192.168.7.4',--被访问的服务器别名(习惯上直接使用目标服务器IP,或取个别名如:JOY) @srvproduct='', @provider='SQLOLEDB', @datasrc='192.168.7.4' --要访问的服务器 go  EXEC sp_addlinkedsrvlogin '192.168.7.4', --被访问的服务器别名(如果上面sp_addlinkedserver中使用别名JOY,则这里也是JOY) 'false', NULL, 'sa', --帐号 '1234567890' --密码goselect * from sys.servers;

然后使用下面的SQL语句插入数据:

insert into [Biz_Customer] select * from Biz_Customer_Master where id='7B210173-7382-43EB-BC5E-0000C3BA564A'

查询报错,某个列的数据类型错误,打开表一看,原来是 发布库上的表的字段顺序跟订阅库上不一样,因为当初做订阅的时候,为了解决Timestamp 问题,将订阅库的Timestamp字段修改成了binary(8)类型,故订阅库上表的字段顺序改变了。

此时,只需要在insert 和 select 语句上,指定相同顺序的列就可以了。那么如何获取表所有的列名称?

很简单,直接选择某个表,新建查询,生成的SQL语句就包含表所有的字段了。
最后正确的语句如下:

insert into [TB_Customer]([Id]      ,[CustomerId]      ,[Code]      ,[Name]      ,[BusinessId]      ,[CreatedOn]      ,[CreatedById]      ,[ModifiedOn]      ,[ModifiedById]      ,[AppraiseTableType]      ,[Timestamp]      )SELECT  [Id]      ,[CustomerId]      ,[Code]      ,[Name]      ,[BusinessId]      ,[CreatedOn]      ,[CreatedById]      ,[ModifiedOn]      ,[ModifiedById]      ,[AppraiseTableType]      ,[Timestamp]  FROM dbo.TB_Customer_Master   where id='7B210173-7382-43EB-BC5E-0000C3BA564A';

经过这样的方式,很方便的把发布库的数据就补充到订阅库上了,之后,数据库的发布订阅错误就解决了。

修改订阅库存储过程

 但是,如果这样的错误很多,每次都去靠手工修补数据是不行的,所以我们还需要找到订阅库上的系统存储过程,做相应的修改。

  •   修改数据,对应的存储过程名字是 sp_MSupd_dboTableName ,所以我们可以拿到要操作的表名字:dbo.TableName
  •   删除数据,对应的存储过程名字是 sp_MSdel_dboTableName,所以我们可以拿到要操作的表名字:dbo.TableName

如果是删除数据,直接把存储过程中的下面内容注释:

if @@rowcount = 0    if @@microsoftversion>0x07320000        exec sp_MSreplraiserror 20598

如果是修改数据,首先也要把上面的内容注释,然后在存储过程的最后,添加下面这样的代码:

if @@rowcount = 0begininsert into [TB_Customer]([Id]      ,[CustomerId]      ,[Code]      ,[Name]      ,[BusinessId]      ,[CreatedOn]      ,[CreatedById]      ,[ModifiedOn]      ,[ModifiedById]      ,[AppraiseTableType]      ,[Timestamp]      )SELECT  [Id]      ,[CustomerId]      ,[Code]      ,[Name]      ,[BusinessId]      ,[CreatedOn]      ,[CreatedById]      ,[ModifiedOn]      ,[ModifiedById]      ,[AppraiseTableType]      ,[Timestamp]  FROM [192.168.7.4].XXDB.dbo.Biz_Customer  where id=@pkc1end

这里没有使用同义词,而是直接使用远程服务器名字加数据库名字方式指定远程表名字,当你要修改的存储过程比较多,推荐采用这种方式而不是同义词。

参数 @pkc1 是存储过程使用的主键参数,每个存储过程都是这样的。

使用游标生成修改语句

但是,如果要修改从存储过程很多,这样一个个的去手工修改存储过程是非常麻烦的,所以我们可以把上面的过程,写一个T-SQL来输出,我们使用游标来便利表所有的列,生成语句:

declare @ObjTbName varchar(100)declare @ColName varchar(100)declare @ColType varchar(50)declare @AllColName varchar(max)declare @SqlText varchar(max)set @ObjTbName='TB_Customer'set @SqlText ='insert into '+@ObjTbName+'('DECLARE column_cursor CURSOR FORSELECT COLUMN_NAME,DATA_TYPE FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME=@ObjTbNameOPEN column_cursorFETCH NEXT FROM column_cursor into @ColName,@ColTypeset @AllColName ='['+ @ColName+']'WHILE @@FETCH_STATUS = 0BEGIN   -- This is executed as long as the previous fetch succeeds.   --print 'Col Name:'+ @ColName +',Col Type:' + @ColType   FETCH NEXT FROM column_cursor into @ColName,@ColType   if @@FETCH_STATUS = 0   --print ' ,'+@ColName   set @AllColName = @AllColName +',['+ @ColName+']'ENDCLOSE column_cursorDEALLOCATE column_cursor--print @AllColNameset @SqlText =@SqlText + char(10)+ @AllColName +')' +CHAR(10)set @SqlText =@SqlText +'select '+CHAR(10) + @AllColName + CHAR(10)set @SqlText =@SqlText +' from [192.168.7.4].XXDB.dbo.'+@ObjTbName + ' where id=@pkc1 'print '--if @@rowcount = 0'print '--    if @@microsoftversion>0x07320000'print '--        exec sp_MSreplraiserror 20598'print 'end 'print 'end 'print 'if @@rowcount = 0'print 'begin'print @SqlTextprint 'end '

将输消息复制粘贴在要修改的存储过程尾部即可。

修改并执行这个存储过程,等订阅代理重新执行这个存储过程后,数据就过去了。

为了方便这个这个过程被程序调用,可以将它封装成存储过程,具体内容如下:

/*--创建数据库复制的时候订阅库修改使用的存储过程--具体原理和使用,请参考博客文章:--  http://www.cnblogs.com/bluedoctor/p/5680582.html --作者:请参考博客文章作者--时间:2016.7.20--调用示例:  exec BuildReplUpdateTable 'MainSqlServer','HRDB','TB_AuditOrgBalance',1*/create procedure BuildReplUpdateTable    @LinkServer varchar(100),   @ObjDBName varchar(50),   @ObjTbName varchar(100),   @IsSp_MSupd bitasbegin    declare @ColName varchar(100)    declare @ColType varchar(50)    declare @AllColName varchar(max)    declare @SqlText varchar(max)    declare @TempText varchar(max)    set @SqlText ='insert into '+@ObjTbName+'('    DECLARE column_cursor CURSOR FOR    SELECT COLUMN_NAME,DATA_TYPE FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME=@ObjTbName    OPEN column_cursor    FETCH NEXT FROM column_cursor into @ColName,@ColType    set @AllColName ='['+ @ColName+']'    WHILE @@FETCH_STATUS = 0    BEGIN       --print 'Col Name:'+ @ColName +',Col Type:' + @ColType       FETCH NEXT FROM column_cursor into @ColName,@ColType       if @@FETCH_STATUS = 0           set @AllColName = @AllColName +',['+ @ColName+']'    END    CLOSE column_cursor    DEALLOCATE column_cursor    set @SqlText =@SqlText + char(10)+ @AllColName +')' +CHAR(10)    set @SqlText =@SqlText +'select '+CHAR(10) + @AllColName + CHAR(10)    set @SqlText =@SqlText +' from ['+@LinkServer+'].['+@ObjDBName +'].[dbo].['+@ObjTbName + '] where id=@pkc1 '    if @IsSp_MSupd = 1     begin      set @TempText='--if @@rowcount = 0'+CHAR(10)+                    '--    if @@microsoftversion>0x07320000' +CHAR(10)+                    '--        exec sp_MSreplraiserror 20598'+CHAR(10)+                    'end '+CHAR(10)+                    'end '+CHAR(10)+                    'if @@rowcount = 0'+CHAR(10)+                    'begin'+CHAR(10)+                    @SqlText +CHAR(10)+                    'end '       select @TempText            end    else    begin       select @SqlText    endend

 虽然上面封装的存储过程可以很方便的生成修改订阅存储过程的部分修改语句,但是如果系统的表很多,目前还没有做到批量的全部修改这些订阅存储过程,如果有一种方法及时通知DBA 哪些订阅数据出现了问题,然后再按照前面的方法解决问题,就很方便了。这个功能,就是下面说的方法。

SQL邮件监控订阅错误

SQL邮件提供了监视数据库各种性能,问题,警报,然后发邮件通知管理员的功能,我们也可以利用这个功能,当订阅库发生数据同步错误,发一封邮件及时通知管理员,而不用实时去盯着“复制监视器”,查看问题了。

  • 首先在“服务器”-管理-数据库邮件节点上,配置一个数据库邮件账号,具体过程略,请参考其它相关文章;
  • 然后,在Sql Server 代理-操作员功能上,添加一个操作员,填写上通知该操作员的电子邮件账号;
  • 最后,在Sql Server 代理-作业节点,选择用于订阅的作业名称,然后打开属性窗口,进行如下设置:

如图填写上一个合适的重试次数,默认这是一个很大的数字,所以会重试很久都不会发出问题邮件。该问题我查找了很久才发现,大家不用走弯路了。

经过这样的配置之后,出现订阅同步问题,会收到大概如下的邮件内容:

作业运行:    “DNXSQL-HRDB-XX发布-DNXSQL1-HRDB-3D57B9A6-207B-486A-8B5D-41125B68A876”已在 2016/7/22 14:00:46 运行持续时间:    0 小时,8 分钟,55 秒状态: 失败消息:    该作业失败。  用户 sa 调用了该作业。最后运行的是步骤 1 (运行代理。)。.

收到该邮件后,去服务器按照前面介绍的方法,解决此问题即可。
至此,DBA可以放心去干别的事情了。

(注:本文是一个业余DBA奋战N多天,不断尝试总结,数次修订本文而成,转载请注明作者,并欢迎使用,它的数据库工具将会提供自动生成修改的订阅存储过程的功能。)

补充:

如果订阅库少了某些记录,可以通过下面类似的查询解决:

update [MainSqlServer].[XXDB].[dbo].TB_Appropriation set ModifiedOn=GETDATE () where ID in (SELECT ID  FROM [MainSqlServer].[XXDB].[dbo].TB_Appropriation where id not in (  SELECT ID FROM [XXDB].[dbo].TB_Appropriation))

其中,MainSqlServer是发布服务器对应的链接服务器名称,假设要补充缺失数据的表有一个ModifiedOn 字段。

 

    本文转自深蓝医生博客园博客,原文链接:http://www.cnblogs.com/bluedoctor/p/5680582.html,如需转载请自行联系原作者

你可能感兴趣的文章
统一沟通-技巧-11-Lync-联盟-无法-音频-远程桌面-传文件
查看>>
书摘—你不可不知的心理策略
查看>>
【博客话题】毕业——开始人生的艰苦历程
查看>>
Linux安装telnet
查看>>
sap scriptfom 多语言翻译
查看>>
黄聪:3分钟学会sessionStorage用法
查看>>
Entity Framework 全面教程详解(转)
查看>>
Windows上Python2.7安装Scrapy过程
查看>>
Chapter 3:Code Style in Django
查看>>
挖掘数据金矿 领军协同创新 曙光荣膺“2016大数据创新应用领袖企业”称号
查看>>
Fast通道获得Win10 Mobile Build 14977更新
查看>>
《BackTrack 5 Cookbook中文版——渗透测试实用技巧荟萃》—第3章3.6节识别操作系统...
查看>>
linux系统防火墙iptables命令规则及配置的示例
查看>>
10 个顶尖的 Linux 开源人工智能工具
查看>>
Firefox 跟踪保护技术将页面加载时间减少 44%
查看>>
聚合(根)、实体、值对象精炼思考总结
查看>>
java解析虾米音乐
查看>>
rails将类常量重构到数据库对应的表中之三
查看>>
mysql 多行合并函数
查看>>
【案例】RAID卡写策略改变引发的问题
查看>>