一次SQLSERVER觸發器編寫感悟
背景:BOSS須要我寫一個工廠採集端到服務器端的數據同步觸發器,數據庫採用的是sqlserver2008
需求:將多臺採集機的數據同步到server中,假設採集端數據庫與server數據庫連接失敗則將數據保存到記錄表中
前期思路:從採集端創建server端的數據庫鏈接,通過採集端的insert,update觸發。同一時候往遠程表寫入
問題:因為初始接觸sqlserver。對sqlserver觸發器了解不深。查閱一些資料後寫出了滿足正常情況下(連接服務器數據庫正常)的觸發器。
create trigger trig_sensor_shengyang
on dbo.sensor_test
begin
--假設原表沒有該記錄則插入該記錄
IF NOT EXISTS(SELECT * FROM deleted)
begin
set NOCOUNT ON;
begin tran
--insertopenrowset(‘sqloledb‘,‘XXX.XXX.XXX.XXX‘;‘DBUSER‘;‘DBPWD‘,bwdb.dbo.test)
--向server表插入該條數據
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)
--更新server表記錄
update shengyang
--推斷假設記錄表中存在對該條數據的記錄,則更新記錄表中的記錄
--(針對記錄表中同一時候存在對同一條數據的create,update。僅僅須要記錄終於unid。
--假設有create終於仍然向server表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塊裏面運行可能出現異常的——往遠程server端寫入的代碼,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
--推斷server表中是否存在該記錄
--假設不存在向server表插入該條數據
print ‘1111111111‘
if not EXISTS(SELECT * FROM shengyang.bwdb.dbo.test where [email protected]_id)
begin
--insert openrowset(‘sqloledb‘,‘XXX.XXX.XXX.XXX‘;‘DBUSER‘;‘DBPWD‘,bwdb.dbo.test)
insert into shengyang.bwdb.dbo.test select * from inserted
end
--否則更新server表數據
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)
--更新server表記錄
update shengyang.bwdb.dbo.test set unid = inserted.unid from inserted where test.sensor_id = @sensor_id
end try
--假設出錯,推斷假設記錄表中存在對該條數據的記錄。則更新記錄表中的記錄
--(針對記錄表中同一時候存在對同一條數據的create,update。僅僅須要記錄終於unid,
--假設有create終於仍然向server表create,假設是多次更新僅僅需記錄最後一次更新)
begin catch
if exists(select * from dbo.test_bak where [email protected]_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提了個建議。通過存儲過程中先做異常處理。推斷server數據庫是否連接成功。隨即寫了個存儲過程,在存儲過程中訪問遠程數據庫。定義一個變量初始值,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)
--調用存儲過程推斷遠程連接server以及同步事務開啟是否成功。返回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
--推斷server表中是否存在該記錄
--假設不存在向server表插入該條數據
if not EXISTS(SELECT * FROM shengyang.bwdb.dbo.test where [email protected]_id)
begin
insert into shengyang.bwdb.dbo.test select * from inserted
end
--否則更新server表數據
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
--更新server表記錄
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 [email protected]_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
觸發器:(非常easy,測試就是通過一個遠程查詢語句推斷)
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
這樣的方法作為推斷是可行的。可是。。。。
。。在觸發器中調用的時候,假設遠程server數據庫連接不上了(測試關閉數據庫服務),觸發的時候直接就報錯了,
其它的代碼根本就沒有運行。
終於。
。
。
找到了推斷遠程鏈接的方法(此時的心情是激動的。)
sp_testlinkedserver (Transact-SQL)
https://msdn.microsoft.com/zh-cn/library/ms189809(v=sql.90).aspx通過該方法可直接推斷創建的遠程server連接是否有效。
。
終於觸發器測試代碼例如以下:
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)
--調用存儲過程推斷遠程連接server以及同步事務開啟是否成功,返回則表示失敗
--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
--推斷server表中是否存在該記錄
--假設不存在向server表插入該條數據
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
--否則更新server表數據
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
--更新server表記錄
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的語錄):問題總是能找到解決方式的,僅僅要你摸清楚設計者的思路,所以一定要多想為什麽,人家為啥要這麽設計 !
遇到問題能夠嘗試用不同的方法解決,但不能一味的依照自己的思路走,從問題的根源。從設計者的角度考慮解決方案,總會尋找到的!
一次SQLSERVER觸發器編寫感悟