一次SQLSERVER觸發器編寫感悟

背景:BOSS需要我寫一個工廠採集端到服務器端的數據同步觸發器,數據庫採用的是sqlserver2008

需求:將多臺採集機的數據同步到服務器中,如果採集端數據庫與服務器數據庫連接失敗則將數據保存到記錄表中

前期思路:從採集端創建服務器端的數據庫鏈接,通過採集端的insert,update觸發,同時往遠程表寫入

問題:由於初始接觸sqlserver,對sqlserver觸發器瞭解不深,查閱一些資料後寫出了滿足正常情況下(連接服務器數據庫正常)的觸發器。

create trigger trig_sensor_shengyang

on dbo.sensor_test for insert,update as

begin

--如果原表沒有該記錄則插入該記錄

IF NOT EXISTS(SELECT * FROM deleted)

begin

set NOCOUNT ON;

begin tran

--insertopenrowset('sqloledb','XXX.XXX.XXX.XXX';'DBUSER';'DBPWD',bwdb.dbo.test)

--向服務器表插入該條數據

insert into shengyang.bwdb.dbo.test select * from inserted

--同時向記錄表中插入數據

insert into dbo.test_bak values((select unid from inserted),(select sensor_id from inserted),'create')

commit tran

end

else

--如果原表存在該記錄則更新該記錄

begin

set NOCOUNT ON;

begin tran

--update openrowset('sqloledb','XXX.XXX.XXX.XXX';'DBUSER';'DBPWD',bwdb.dbo.test)

--更新服務器表記錄

update shengyang.bwdb.dbo.test set unid = inserted.unid from inserted

--判斷如果記錄表中存在對該條數據的記錄,則更新記錄表中的記錄

--(針對記錄表中同時存在對同一條數據的create,update,只需要記錄最終unid

--如果有create最終仍然向服務器表create,如果是多次更新只需記錄最後一次更新)

if exists(select * from dbo.test_bak where sensor_id=(select sensor_id from inserted))

begin

update dbo.test_bak set unid=i.unid from inserted i

end

--如果記錄表中不存在對該條數據的修改記錄,則在記錄標中插入該數據的update記錄

else

begin

insert into dbo.test_bak values ((select unid from inserted),(select sensor_id from inserted),'update')

end

commit tran

end

end


但是由於需要考慮雙方網絡不通的情況,因此需要做異常處理。開始沒查找到判斷遠程數據庫連接的方法,因此想着直接通過try catch來實現(try塊裏面執行可能出現異常的——往遠程服務器端寫入的代碼,catch塊裏寫往採集端本地記錄表中的代碼)

create trigger trig_sensor_shengyang 
on dbo.sensor_test after insert,update as
declare @unid varchar(20)
declare @sensor_id varchar(8)
declare @boolean varchar(1)
begin
set @unid = (select unid from inserted)
set @sensor_id = (select sensor_id from inserted)


--如果採集端原表沒有該記錄則插入該記錄
IF NOT EXISTS(SELECT * FROM deleted)
begin
set NOCOUNT ON;
begin try
-- BEGIN TRAN
--判斷服務器表中是否存在該記錄
--如果不存在向服務器表插入該條數據
print '1111111111'
if not EXISTS(SELECT * FROM shengyang.bwdb.dbo.test where sensor_id=@sensor_id)
begin
--insert openrowset('sqloledb','XXX.XXX.XXX.XXX';'DBUSER';'DBPWD',bwdb.dbo.test)
insert into shengyang.bwdb.dbo.test select * from inserted
end
--否則更新服務器表數據
else
begin
update shengyang.bwdb.dbo.test set unid = inserted.unid from inserted where test.sensor_id = @sensor_id
end
--COMMIT TRAN
end try

--如果出錯則向採集端記錄表中插入數據
begin catch
print 'fail to insert this data to server'
rollback
-- print @@TRANCOUNT
-- IF @@TRANCOUNT > 0---------------判斷有沒有事務
-- BEGIN
-- ROLLBACK TRANSACTION ts----------回滾事務
-- END 
insert into dbo.test_bak values (@unid,@sensor_id,'insert')
set @boolean = '1'

--EXEC insert_sensor_shengyang @unid,@sensor_id
end catch
-- if @boolean='1'
-- begin
-- print 'boolean'+@boolean
-- insert into dbo.test_bak values (@unid,@sensor_id,'insert')
-- end
end
else
--如果採集端原表存在該記錄則更新該記錄
begin
set NOCOUNT ON; 
begin try
--update openrowset('sqloledb','XXX.XXX.XXX.XXX';'DBUSER';'DBPWD',bwdb.dbo.test)
--更新服務器表記錄
update shengyang.bwdb.dbo.test set unid = inserted.unid from inserted where test.sensor_id = @sensor_id
end try
--如果出錯,判斷如果記錄表中存在對該條數據的記錄,則更新記錄表中的記錄
--(針對記錄表中同時存在對同一條數據的create,update,只需要記錄最終unid,
--如果有create最終仍然向服務器表create,如果是多次更新只需記錄最後一次更新)
begin catch
if exists(select * from dbo.test_bak where sensor_id=@sensor_id)
begin
update dbo.test_bak set unid=i.unid from inserted i
end
--如果記錄表中不存在對該條數據的修改記錄,則在記錄標中插入該數據的update記錄
else
begin
insert into dbo.test_bak values (@unid,@sensor_id,'update')
end
end catch
end
end

但是無論怎樣,只要出現異常,就會強制回滾。此時如果在catch塊之前提交,觸發的仍然時候就會報錯,並且無法將錯誤的記錄插入異常記錄表(執行不到),觸發的原表記錄可以寫入。如果在catch塊中rollback,然後將該記錄插入異常記錄表可以,但是同時回滾後觸發的原記錄也回滾丟失了。如果在catch塊中commit,也不行(catch塊中默認回滾了所有事務),包括嘗試了使用記錄回滾點進行分段事務提交回滾還是無法解決。既不能commit,又不能rollback,這如何是好。。。。。。

隨後BOSS提了個建議,通過存儲過程中先做異常處理,判斷服務器數據庫是否連接成功。隨即寫了個存儲過程,在存儲過程中訪問遠程數據庫,定義一個變量初始值,catch塊中修改這個值,然後把這個值作爲存儲過程返回值進行判斷。

觸發器:

create trigger trig_sensor_shengyang 
on dbo.sensor_test after insert,update as
declare @unid varchar(20)
declare @sensor_id varchar(8)
declare @boolean varchar(1)
declare @ifconnected varchar(2)
begin
set @unid = (select unid from inserted)
set @sensor_id = (select sensor_id from inserted)

--調用存儲過程判斷遠程連接服務器以及同步事務開啓是否成功,返回1則表示失敗
--sp_testlinkedserver [ @servername ] = servername
EXEC @ifconnected = [boolean_if_connected] 
print @ifconnected
--如果遠程連接成功
IF @ifconnected != 1
--如果採集端原表沒有該記錄則插入該記錄
IF NOT EXISTS(SELECT * FROM deleted)
begin
set NOCOUNT ON;
begin try
--判斷服務器表中是否存在該記錄
--如果不存在向服務器表插入該條數據
if not EXISTS(SELECT * FROM shengyang.bwdb.dbo.test where sensor_id=@sensor_id)
begin
insert into shengyang.bwdb.dbo.test select * from inserted
end
--否則更新服務器表數據
else
begin
update shengyang.bwdb.dbo.test set unid = inserted.unid from inserted where test.sensor_id = @sensor_id
end
end try
begin catch
print 'failed to insert this data to server'
rollback
end catch
end
else
--如果採集端原表存在該記錄則更新該記錄
begin
set NOCOUNT ON; 
begin try
--更新服務器表記錄
update shengyang.bwdb.dbo.test set unid = inserted.unid from inserted where test.sensor_id = @sensor_id
end try
begin catch
print 'failed to update this date to server'
rollback
end catch
end
else
if exists(select * from dbo.test_bak where sensor_id=@sensor_id)
begin
begin tran
update dbo.test_bak set unid=i.unid from inserted i
commit tran
end
--如果記錄表中不存在對該條數據的修改記錄,則在記錄標中插入該數據的update記錄
else
begin
begin tran
insert into dbo.test_bak values (@unid,@sensor_id,'....')
commit tran
end
end

觸發器:(很簡單,測試就是通過一個遠程查詢語句判斷)

CREATE PROCEDURE boolean_if_connected
AS
BEGIN 
declare @flag varchar(1)
begin try
set @flag='0'
select * from shengyang.bwdb.dbo.test;
end try
begin catch
set @flag='1'
print @flag
end catch
  return @flag
end

這種方法作爲判斷是可行的,但是。。。。。。在觸發器中調用的時候,如果遠程服務器數據庫連接不上了(測試關閉數據庫服務),觸發的時候直接就報錯了,


其他的代碼根本就沒有執行。

最終。。。 找到了判斷遠程鏈接的方法(此時的心情是激動的。)

sp_testlinkedserver (Transact-SQL)

https://msdn.microsoft.com/zh-cn/library/ms189809(v=sql.90).aspx
通過該方法可直接判斷創建的遠程服務器連接是否有效。。
最終觸發器測試代碼如下:

create trigger trig_sensor_shengyang

on dbo.sensor_test after insert,update as

declare @unid varchar(20)

declare @sensor_id varchar(8)

declare @boolean varchar(1)

declare @ifconnected varchar(2)

begin

set @unid =(select unid from inserted)

set @sensor_id =(select sensor_id from inserted)

--調用存儲過程判斷遠程連接服務器以及同步事務開啓是否成功,返回則表示失敗

--sp_testlinkedserver[ @servername ] = servername

EXEC @ifconnected = [sp_testlinkedserver]shengyang

print @ifconnected

--如果遠程連接成功

IF @ifconnected != 1

--如果採集端原表沒有該記錄則插入該記錄

    IF NOTEXISTS(SELECT * FROM deleted)  

       begin

           set NOCOUNT ON;     

           begin try

--判斷服務器表中是否存在該記錄

--如果不存在向服務器表插入該條數據

              if not EXISTS(SELECT * FROM shengyang.bwdb.dbo.test where sensor_id=@sensor_id)

                  begin

                     insert into shengyang.bwdb.dbo.test select * from inserted

                  end

--否則更新服務器表數據

              else

                  begin

                     update shengyang.bwdb.dbo.test set unid = inserted.unid from inserted where test.sensor_id = @sensor_id

                  end

           end try

           begin catch

              print 'failed to insert to server'

              rollback

           end catch

       end

    else

--如果採集端原表存在該記錄則更新該記錄

       begin

           set NOCOUNT ON;

           begin try

--更新服務器表記錄

              update shengyang.bwdb.dbo.test set unid = inserted.unid from inserted where test.sensor_id = @sensor_id

           end try

           begin catch

              print 'failed to update to server'

              rollback

           end catch

       end

else

    if exists(select * from dbo.test_bak where sensor_id=@sensor_id)

              begin

                  begin tran

                  update dbo.test_bak set unid=i.unid from inserted i

                  commit tran

              end

--如果記錄表中不存在對該條數據的修改記錄,則在記錄標中插入該數據的update記錄

           else

              begin

                  begin tran

                  insert into dbo.test_bak values (@unid,@sensor_id,'....')

                  commit tran

              end

end

 

總結(用BOSS的語錄):問題總是能找到解決方案的,只要你摸清楚設計者的思路,所以一定要多想爲什麼,人家爲啥要這麼設計 !
遇到問題可以嘗試用不同的方法解決,但不能一味的按照自己的思路走,從問題的根源,從設計者的角度考慮解決方式,總會尋找到的!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章