1、背景
环境:发布服务器A Windows2008+SQL2008,分发服务器B Windows2008+SQL2008,订阅服务器C Windows2008+SQL2012
发布服务器A上的用户信息表member,通过事务复制,推送到订阅器C上的用户信息表member。订阅服务器C上存在一张用户账户表,之前的机制是通过在用户信息表上的insert触发器往用户账户表初始化数据。
问题:发布服务器上的用户信息表的某一字段与订阅服务器上的用户信息表的某一字段内容不一致,近一个月出现了两次,运营反馈这个问题已经持续很长一段时间,希望我们能彻底解决,而不是每次用户反馈了再查再同步!
处理:主库导一份用户数据到订阅数据库上,对比不一致的字段有多少记录,无意中发现订阅的数据量少于发布的记录数。问题重心由字段不一致转移到数量不一致,是什么原因导致记录条数都不一样呢?启动复制监视器,查看分发到订阅的历史记录,发现下面的错误信息,查看这些时间点主库有记录插入,但订阅上找不到对应记录。
此时基本可以想到是插入过程中出了问题,如果有分发库上的权限完全可以查出具体的错误信息。现在假设数据已经传到订阅数据库,由于某种原因,导致数据没能插入成功。
2、模拟触发器
模拟生产环境下通过触发器将用户表中的数据插入到用户账户信息表。
1 create table test1 (id int ,name varchar(20),date datetime default getdate()) 2 create table test2 (id int ,name varchar(20),date datetime default getdate()) 3 create unique nonclustered index [uniqueindex] on [dbo].[test2] ([id] asc) 4 create trigger tr_test1 5 on test1 6 for insert 7 as 8 begin 9 insert into test2(id,name,date) 10 select * from inserted 11 end 12 --连续插入两次 13 insert into test1 select 1,'abs',getdate()
test2有唯一索引,当往test1中插入相同记录时,会失败(test1、test2插入失败)。
如何将失败的记录获取出来,尝试在触发器用try-catch,并把失败的记录插入到另一张表test3中
1 create table test3 (id int ,name varchar(20),date datetime default getdate()) 2 --修改触发器 3 alter trigger tr_test1 on test1 4 for insert 5 as 6 begin 7 begin try 8 insert into test2(id,name,date ) 9 select * from inserted 10 end try 11 begin catch 12 insert into test3(id,name,date ) 13 select * from inserted 14 end catch 15 end 16 --插入测试数据 17 insert into test1 select 1,'abs',getdate()
当插入相同记录时,同样报错,只是错误信息有所不同
群里请教别人,很快有群友回复
alter trigger tr_test1 on test1 for insert as begin set xact_abort off begin try insert into test2(id,name,date ) select * from inserted end try begin catch insert into test3(id,name,date ) select * from inserted end catch end --插入测试数据 insert into test1 select 1,'ere',getdate()
重点是SET XACT_ABORT OFF,此时往Test1插入id已存在于Test2中的记录,就不会报之前的错误。查询三张表的记录如下:
至此已经明白订阅表为什么与发布表不一致。主库每天会按照一定规则从用户信息表删除僵尸用户,订阅表同步删除,但是用户账户表前期并没有删除对应记录。之后ID重用,往订阅端用户信息表写入数据的同时,由于触发器需写入到用户账户表,但是用户账户表在ID字段有唯一约束,insert失败!就出现发布与订阅不一致的情况。
解决方法:确保从用户信息表删除僵尸用户的同时,也从用户账户表删除记录。还可以适当修改触发器让已存在用户账户表的新记录写入到异常用户日志表,方便核对!
3、延伸
将表Test1重命名,然后再创建同结构的Test1,之后在Test1上创建一个Insert触发器。通过sp_helptext triggername查看,此时有两个触发器在Test1上。可实际用的应该是哪个?
1 --重命名test1表 2 sp_rename 'test1','test1_alias','object' 3 --重新创建test1数据表 4 create table test1 (id int ,name varchar(20),date datetime default getdate()) 5 --重新创建test1上的触发器,为区分跟踪在里面增加一行注释 6 create trigger tr_test1_alias on test1 7 for insert 8 as 9 begin 10 set xact_abort off 11 begin try 12 insert into test2(id,name,date )--区分跟踪 13 select * from inserted 14 end try 15 begin catch 16 insert into test3(id,name,date )--区分跟踪 17 select * from inserted 18 end catch 19 end
此时用sp_helptext查看触发器的定义,发现两个都是在test1上
在sysobjects中看触发器的parent_object
1 --先开启跟踪,然后插入记录 2 insert into test1 select 2,'tst',getdate()
跟踪信息如下,图片中可以看出插入数据使用的是后面创建的触发器tr_test1_alias
此时查询三张表中的记录信息如下所示:
可以在test1_alais上插入数据,看其上的触发器能否使用
1 --先开启跟踪,然后插入记录 2 insert into test1_alias select 3,'uyu',getdate()
从跟踪信息和数据结果可以看出插入数据使用的是触发器tr_test1,虽然sp_helptext tr_test1返回是在test1上,但如果我们在对象资源管理器中,右击对应触发器然后修改或编辑触发器脚本到新窗口,可以看到其指向的是test1_alias。重命名表,与其相关的触发器的parent_object_id指向新表,触发器以对象资源管理器下看到的为准。