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

本文涉及的产品
云数据库 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
相关文章
|
7天前
|
SQL 人工智能 算法
【SQL server】玩转SQL server数据库:第二章 关系数据库
【SQL server】玩转SQL server数据库:第二章 关系数据库
47 10
|
19天前
|
SQL 存储 关系型数据库
一文搞懂SQL优化——如何高效添加数据
**SQL优化关键点:** 1. **批量插入**提高效率,一次性建议不超过500条。 2. **手动事务**减少开销,多条插入语句用一个事务。 3. **主键顺序插入**避免页分裂,提升性能。 4. **使用`LOAD DATA INFILE`**大批量导入快速。 5. **避免主键乱序**,减少不必要的磁盘操作。 6. **选择合适主键类型**,避免UUID或长主键导致的性能问题。 7. **避免主键修改**,保持索引稳定。 这些技巧能优化数据库操作,提升系统性能。
215 4
一文搞懂SQL优化——如何高效添加数据
|
1月前
|
SQL 数据可视化 数据处理
使用SQL和Python处理Excel文件数据
使用SQL和Python处理Excel文件数据
52 0
|
7天前
|
SQL 算法 数据库
【SQL server】玩转SQL server数据库:第三章 关系数据库标准语言SQL(二)数据查询
【SQL server】玩转SQL server数据库:第三章 关系数据库标准语言SQL(二)数据查询
64 6
|
29天前
|
SQL 数据库
sqlserver建库建表建约束,删库删表删约束的示例总结
sqlserver建库建表建约束,删库删表删约束的示例总结
12 0
|
29天前
|
SQL 安全 数据库
第三章用sql语句操作数据
第三章用sql语句操作数据
10 0
|
1月前
|
数据库
SQLSERVER 2014 删除数据库定时备份任务提示失败DELETE 语句与 REFERENCE 约束“FK_subplan_job_id“冲突
SQLSERVER 2014 删除数据库定时备份任务提示失败DELETE 语句与 REFERENCE 约束“FK_subplan_job_id“冲突
|
1月前
|
SQL 数据库 数据库管理
SQL中如何添加数据:基础指南
SQL中如何添加数据:基础指南
24 2
|
2月前
|
SQL 数据库 数据安全/隐私保护
sql注入碰到加密数据怎么办
sql注入碰到加密数据怎么办
19 1
|
1月前
|
SQL 数据库 数据安全/隐私保护
Sql Server数据库Sa密码如何修改
Sql Server数据库Sa密码如何修改