使用SQLServer同义词和SQL邮件,解决发布订阅中订阅库丢失数据的问题

本文涉及的产品
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
云数据库 RDS SQL Server,基础系列 2核4GB
简介:

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

定位缺失数据

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

复制代码
尝试的命令:
if @@trancount > 0 rollback tran
(事务序列号: 0x0000992600000D09007F00000000,命令 ID: 19)

错误消息:
应用复制的命令时在订阅服务器上找不到该行。 (源: MSSQLServer,错误号: 20598)
获取帮助: http://help/20598
应用复制的命令时在订阅服务器上找不到该行。 (源: MSSQLServer,错误号: 20598)
复制代码

 

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

use distribution
go
sp_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' --密码
go

select * 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 = 0
begin
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 [192.168.7.4].XXDB.dbo.Biz_Customer
  where id=@pkc1

end
复制代码


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

参数 @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 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
   -- 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+']'
END

CLOSE column_cursor
DEALLOCATE column_cursor
--print @AllColName

set @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 @SqlText
print '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 bit
as
begin
    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
    end

end
复制代码

 虽然上面封装的存储过程可以很方便的生成修改订阅存储过程的部分修改语句,但是如果系统的表很多,目前还没有做到批量的全部修改这些订阅存储过程,如果有一种方法及时通知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多天,不断尝试总结,数次修订本文而成,转载请注明作者,并欢迎使用 SOD开发框架,它的数据库工具将会提供自动生成修改的订阅存储过程的功能。)

补充:

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

复制代码
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,如需转载请自行联系原作者




相关实践学习
使用SQL语句管理索引
本次实验主要介绍如何在RDS-SQLServer数据库中,使用SQL语句管理索引。
SQL Server on Linux入门教程
SQL Server数据库一直只提供Windows下的版本。2016年微软宣布推出可运行在Linux系统下的SQL Server数据库,该版本目前还是早期预览版本。本课程主要介绍SQLServer On Linux的基本知识。 相关的阿里云产品:云数据库RDS SQL Server版 RDS SQL Server不仅拥有高可用架构和任意时间点的数据恢复功能,强力支撑各种企业应用,同时也包含了微软的License费用,减少额外支出。 了解产品详情: https://www.aliyun.com/product/rds/sqlserver
相关文章
|
27天前
|
SQL 存储 缓存
SQL Server 数据太多如何优化
11种优化方案供你参考,优化 SQL Server 数据库性能得从多个方面着手,包括硬件配置、数据库结构、查询优化、索引管理、分区分表、并行处理等。通过合理的索引、查询优化、数据分区等技术,可以在数据量增大时保持较好的性能。同时,定期进行数据库维护和清理,保证数据库高效运行。
|
2月前
|
SQL 移动开发 Oracle
SQL语句实现查询连续六天数据的方法与技巧
在数据库查询中,有时需要筛选出符合特定时间连续性条件的数据记录
|
2月前
|
SQL 存储 关系型数据库
添加数据到数据库的SQL语句详解与实践技巧
在数据库管理中,添加数据是一个基本操作,它涉及到向表中插入新的记录
|
2月前
|
SQL 数据挖掘 数据库
SQL查询每秒的数据:技巧、方法与性能优化
id="">SQL查询功能详解 SQL(Structured Query Language,结构化查询语言)是一种专门用于与数据库进行沟通和操作的语言
|
3月前
|
SQL 数据库
数据库数据恢复—SQL Server数据库报错“错误823”的数据恢复案例
SQL Server附加数据库出现错误823,附加数据库失败。数据库没有备份,无法通过备份恢复数据库。 SQL Server数据库出现823错误的可能原因有:数据库物理页面损坏、数据库物理页面校验值损坏导致无法识别该页面、断电或者文件系统问题导致页面丢失。
104 12
数据库数据恢复—SQL Server数据库报错“错误823”的数据恢复案例
|
19天前
|
SQL 存储 Linux
从配置源到数据库初始化一步步教你在CentOS 7.9上安装SQL Server 2019
【11月更文挑战第8天】本文介绍了在 CentOS 7.9 上安装 SQL Server 2019 的详细步骤,包括系统准备、配置安装源、安装 SQL Server 软件包、运行安装程序、初始化数据库以及配置远程连接。通过这些步骤,您可以顺利地在 CentOS 系统上部署和使用 SQL Server 2019。
|
20天前
|
SQL 存储 Linux
从配置源到数据库初始化一步步教你在CentOS 7.9上安装SQL Server 2019
【11月更文挑战第7天】本文介绍了在 CentOS 7.9 上安装 SQL Server 2019 的详细步骤,包括系统要求检查与准备、配置安装源、安装 SQL Server 2019、配置 SQL Server 以及数据库初始化(可选)。通过这些步骤,你可以成功安装并初步配置 SQL Server 2019,进行简单的数据库操作。
|
2月前
|
存储 数据挖掘 数据库
数据库数据恢复—SQLserver数据库ndf文件大小变为0KB的数据恢复案例
一个运行在存储上的SQLServer数据库,有1000多个文件,大小几十TB。数据库每10天生成一个NDF文件,每个NDF几百GB大小。数据库包含两个LDF文件。 存储损坏,数据库不可用。管理员试图恢复数据库,发现有数个ndf文件大小变为0KB。 虽然NDF文件大小变为0KB,但是NDF文件在磁盘上还可能存在。可以尝试通过扫描&拼接数据库碎片来恢复NDF文件,然后修复数据库。
|
3月前
|
SQL 关系型数据库 MySQL
创建包含MySQL和SQLServer数据库所有字段类型的表的方法
创建一个既包含MySQL又包含SQL Server所有字段类型的表是一个复杂的任务,需要仔细地比较和转换数据类型。通过上述方法,可以在两个数据库系统之间建立起相互兼容的数据结构,为数据迁移和同步提供便利。这一过程不仅要考虑数据类型的直接对应,还要注意特定数据类型在不同系统中的表现差异,确保数据的一致性和完整性。
36 4
|
3月前
|
SQL 存储 数据管理
SQL Server数据库
SQL Server数据库
63 11