程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> 其他數據庫知識 >> 更多數據庫知識 >> SQL Server觸發器及觸發器中的事務學習

SQL Server觸發器及觸發器中的事務學習

編輯:更多數據庫知識

如果你有對觸發器和事務的概念,有些了解,這篇文章,對你來說會是很簡單,或能讓你更進一步的了解觸發器裡面的一些故事,和觸發器中事務個故事。在這邊文章裡面,我不會從觸發器和事務的概念去講述,而是從常見的兩種觸發器類型(DML觸發器 & DDL觸發器)和After觸發器 &  Instead Of 觸發器的應用不同,開始說起它們,然後是說與事務有關的故事。如果,你有什麼建議和意見,都可以通過文章後面的回復與我溝通,或者通過E-Mail方式,與 我交流;我的Email地址是:[email protected]

     在下面的內容,用到一些SQL Server 觸發器和事務的一些術語,如果有些不明白的地方,可以查閱MSDN資料庫,或SQL Server本地幫助文檔:

DML觸發器(DML Triggers) DDL觸發器(DDL Triggers) 事務模式(Transaction modes) 顯式事務(Explicit Transactions) 自動提交事務(Autocommit Transactions) 隱式事務(Implicit Transactions) 批范圍的事務(Batch-scoped Transactions)

After觸發器 Vs Instead Of觸發器

            After 觸發器將在處理觸發操作(Insert、Update 或 Delete)、Instead Of 觸發器和約束之後激發。Instead Of是將在處理約束前激發,以替代觸發操作。下面兩張圖描述了After觸發器和Instead Of觸發器的執行先後順序。

     image      
image 

     圖1                                                                             圖2

     左邊的圖1,描述了After觸發器執行順序情況,我在這裡通過一個簡單的例子來說明After觸發器的執行順序,以便能加深對左圖1 After觸發器的理解。

先創建表Contact

use tempdb
Go
if object_id('Contact') Is Not null 
 Drop Table Contact
Go
Create Table Contact
(
 ID int Primary Key Identity(1,1),
 Name nvarchar(50),
 Sex nchar(2) Check(Sex In(N'F',N'M')) Default('M')
)
Go

再創建After觸發器tr_Contact

use tempdb
Go
If Exists(Select 1 From sys.triggers Where name='tr_Contact')
 Drop Trigger tr_Contact 
Go
Create Trigger tr_Contact On Contact After Insert
As
Select Name,Sex From Inserted /*顯示Inserted表的內容,用來判斷觸發器執行的先後順序*/
Go

然後Insert數據,判斷After觸發器的執行順序

use tempdb
Go
Insert Into Contact (Name,Sex) Values ('Bill','U')
Go

這裡,在沒有運行Insert語句之前,我們可以判斷,執行Insert過程會觸發Check錯誤,因為字段Sex的值必須是”F” Or “M”,而這裡將要插入的是”U”.好了,再來看運行Insert語句後的情況。

image

本例子,只看到引發Check約束沖突的錯誤,而無法看到Inserted表的數據,說明一點就是,引起Check約束之前,不會引發After觸發器tr_Contact的操作。這就驗證了圖1的After觸發器執行順序情況。

     好了,接下來,我們再測試Instead Of觸發器 圖2的情況;我使用上邊建好的測試表Contact來舉例。

先修改觸發器tr_Contact內容,

use tempdb
Go
If Exists(Select 1 From sys.triggers Where name='tr_Contact')
 Drop Trigger tr_Contact 
Go
Create Trigger tr_Contact On Contact Instead Of Insert
As
print '觸發器作代替執行操作'
Insert Into Contact (Name,Sex) Select Name,Sex From Inserted /*代替觸發器外面的Insert行為*/
Go

再Insert數據,觀察SQL Server執行後的提示信息,

use tempdb
Go
Insert Into Contact (Name,Sex) Values ('Bill','U')
Go

image  

這裡,看到,先是觸發器操作,再是Check約束處理。本例中,在觸發器裡面使用一條Insert的語句來描述觸發器的代替執行操作,這SQL語句通過Select表Inserted得到觸發器外面Insert內容。當SQL Server執行到觸發器裡面的Insert語句,才會引起Check約束處理.倘若,在觸發器tr_Contact沒有Insert的代替行為,那麼就不會出現Check約束處理錯誤的信息(注:沒有Check錯誤信息,並不表示沒有作Check處理)。修改上邊的觸發器tr_Contact內容,做個簡易的驗證.

use tempdb
Go
If Exists(Select 1 From sys.triggers Where name='tr_Contact')
 Drop Trigger tr_Contact 
Go
Create Trigger tr_Contact On Contact Instead Of Insert
As
print '觸發器作代替執行操作'
Go
use tempdb
Go
Insert Into Contact (Name,Sex) Values ('Bill','U')
Go
Select * From Contact

imageimage

可以看到,Instead Of 觸發器tr_Contact內容沒有Insert的SQL語句,不會引發Check處理錯誤,而且檢查Insert動作後的結果,發現表Contact也沒有之前我們Insert的數據。這些足夠驗證了Instead Of觸發器的執行先後順序和代替執行操作。

 

 

 

DML 觸發器 Vs DDL 觸發器


      DML 觸發器在 Insert、Update 和 Delete 語句上操作,可以作為After 觸發器 和 Instead Of 觸發器。

     DDL 觸發器對 Create、Alter、Drop 和其他 DDL 語句以及執行 DDL 式操作的存儲過程執行操作,只可作為After觸發器,不能Instead Of觸發器。

     前面的內容,有描述DML觸發器中的After & Instead Of觸發器內容,下面直接來看DDL的操作順序:

     image

     圖3.

     從圖3.可以知道,在DDL觸發器中,是沒有創建Inserted & Deleted過程的,我們通過簡單的例子去測試下。

     創建一個服務器范圍內的DDL觸發器,檢查有沒有Inserted 表,

use master
Go
If Exists(Select 1 From sys.server_triggers Where name='tr_createDataBase')
 Drop Trigger tr_createDataBase On All Server
Go
Create Trigger tr_createDataBase On All Server After Create_DataBase
As 
Select * From inserted
Go

執行創建數據庫SQL語句,

use master
Go
Create Database myDataBase On Primary
(Name='MyDataBase_Data',Filename='E:\DATA\SQL2008DE01\MyDataBase_Data.mdf') Log On 
(Name='MyDataBase_Log',Filename='E:\DATA\SQL2008DE01\MyDataBase_Log.ldf')
Go

返回錯誤信息,

image

使用上邊相同的方法,我們驗證DDL觸發器中,不會創建Deleted表;是否創建Deleted & Inserted,也可以認為是DDL觸發器與DML觸發器不同之處。在DLL觸發器與DML觸發器不同的一個重要特征是作用域,DML觸發器只能應用在數據庫層(Database Level)的表和視圖上,而DDL觸發器應用於數據庫層(Database Level)和服務器層(Server Level);DDL觸發器的作用域取決於事件。下面簡單描述下事件組的內容。

 

數據庫層事件主要包含:

    DDL Table events: Create table, Alter table, Drop table DDL view events : Create view, Alter view, Drop view DDL trigger events :Create trigger, Drop trigger, Alter trigger DDL synonym events: Create synonym, drop synonym DDL Index events: Create index, Alter index, Drop Index DDL Database level security events: Create User, Drop user, Alter user Create role, Drop role, Alter role Create application role, Drop application role, Alter Application role Create Schema, Drop Schema, Alter Schema Grant database access, Revoke database access, Deny Database access DDL Service broker events: Create Message type, Alter Message type, Drop Message type Create contract, Drop contract, Alter contract Create Service, Alter service, Drop Service Create route, Drop route, Alter route

服務器層事件主要包含:

    Create Database, Drop Database Create Login, Drop Login, Alter Login

 

 

觸發器和事務的故事


      在前面的幾個例子中,如DML觸發器例子,Insert 語句執行後,因為觸發器操作 或 Check處理錯誤,沒有把數據真正的插入到表Contact中。其實,當執行觸發器時,觸發器的操作好像有一個未完成的事務在起作用。 通過幾個例子來講解觸發器和事務的故事。

創建一個表ContactHIST,用於對表Contact作Update Or Delete操作時,把操作前的數據Insert到表ContactHIST中。

use tempdb
Go
if object_id('ContactHIST') Is Not null 
 Drop Table ContactHIST
Go
Create Table ContactHIST
(
 ID int Primary Key Identity(1,1),
 ContactID int,
 Name nvarchar(50),
 Sex nchar(2),
 ActionType nvarchar(10) Check(ActionType In('Update','Delete')),
 LastUpdateDate datetime Default(getdate())
)
Go

修改觸發器tr_Contact內容,

use tempdb
Go
If Exists(Select 1 From sys.triggers Where name='tr_Contact')
 Drop Trigger tr_Contact 
Go
Create Trigger tr_Contact On Contact After Update,Delete
As
Insert Into ContactHIST(ContactID,Name,Sex)
 Select ID,Name,Sex From deleted 
 
Rollback Tran 
 
Begin Tran
Go

測試數據,

use tempdb
Go
Insert Into Contact (Name,Sex) Values ('Bill','F')
Go
--Update
Update Contact 
 Set Sex='M'
 Where Name='Bill'
Go
Select * From Contact
Select * From ContactHIST
Go

測試結果:

imageimage

從上邊的測試情況,看出,Update Contact觸發tr_Contact觸發器操作,觸發器裡面的Rollback Tran 動作導致了觸發器外面的Update語句執行回滾,而Rollback Tran 語句後面的Begin Tran語句,主要是應用於保持整個事務的完整性。為了更能理解這一過程,我模擬了一個觸發器中的事務開始結束過程。

image

圖4.

在SQL Server 2005 和 SQL Server 2008上面,可以看到如圖4.的效果。在低版本的SQL Server上,可能會出現錯誤提示情況,不管如何,在觸發器外面,SQL Server都會Rollback Tran。下面我做個錯誤提示的例子。

修改觸發器tr_Contact內容

use tempdb
Go
If Exists(Select 1 From sys.triggers Where name='tr_Contact')
 Drop Trigger tr_Contact 
Go
Create Trigger tr_Contact On Contact After Update,Delete
As
Insert Into ContactHIST(ContactID,Name,Sex)
 Select ID,Name,Sex From deleted 
 
Rollback Tran 
 
--Begin Tran 
Go

重新執行Update操作,

use tempdb
Go
Update Contact 
 Set Sex='M'
 Where Name='Bill'
Go 
Select @@TRANCOUNT 
Go
Select * From Contact
Select * From ContactHIST
Go

imageimage

在觸發器裡面沒有Begin Tran語句動作,觸發器外面也能回滾操作。這裡我們可以通過查詢表數據和@@Trancount來判斷。

         其實,上面的例子,Update語句,是以自動提交事務(Autocommit Transactions)模式 開始執行的,觸發器裡Rollback Tran後面,不管有沒有Begin Tran ,最後都會事務都會交回給SQL Server自動提交事務管理。當然,在DML觸發器中,你可以使用顯式事務(Explicit Transactions),或開啟隱式事務(Implicit Transactions) 來控制,當然你也可以應用於批范圍的事務(Batch-scoped Transactions) 中。這裡,我通過開啟隱式事務(Implicit Transactions) 的例子來說,觸發器與事務的關系。

修改觸發器tr_Contact的內容,

use tempdb
Go
If Exists(Select 1 From sys.triggers Where name='tr_Contact')
 Drop Trigger tr_Contact 
Go
Create Trigger tr_Contact On Contact After Update,Delete
As
Print N'觸發器裡Insert 前,@@Trancount='+Rtrim(@@Trancount)
 
Insert Into ContactHIST(ContactID,Name,Sex)
 Select ID,Name,Sex From deleted 
 
Print N'觸發器裡Insert後,Rollback Tran 前,@@Trancount='+Rtrim(@@Trancount)
 
Rollback Tran 
 
Print N'觸發器裡Rollback Tran 後,@@Trancount='+Rtrim(@@Trancount)
 
Begin Tran 
Go

開啟隱式事務(Implicit Transactions) 來測試,

use tempdb
Go
Set Implicit_transactions On /**/
Go
Print N'Update Contact前,@@Trancount='+Rtrim(@@Trancount)
 
Update Contact 
 Set Sex='M'
 Where Name='Bill'
 
Print N'Update Contact後,@@Trancount='+Rtrim(@@Trancount)
 
Rollback Tran
 
Print N'觸發器外面Rollback Tran 後,@@Trancount='+Rtrim(@@Trancount)
 
Go 
Set Implicit_transactions Off /**/
Go
 
Go
Select * From Contact
Select * From ContactHIST
Go

 

image

 

這裡,你是否發現一個很有意思的問題,在觸發器理,執行Insert ContactHIST之前,@@Trancount=1,執行Insert後,@@Trancount還是為1,觸發器外面Update Contact後,@@Trancount就變成了2,。這裡可以理解成,你在觸發器裡面,發出一個Begin Tran,那麼SQL Server 就會創建一個嵌套事務。當你在觸發器裡面,在Rollback Tran後面屏蔽掉Begin Tran,就會出現錯誤3609,如,

use tempdb
Go
If Exists(Select 1 From sys.triggers Where name='tr_Contact')
 Drop Trigger tr_Contact 
Go
Create Trigger tr_Contact On Contact After Update,Delete
As
Print N'觸發器裡Insert 前,@@Trancount='+Rtrim(@@Trancount)
 
Insert Into ContactHIST(ContactID,Name,Sex)
 Select ID,Name,Sex From deleted 
 
Print N'觸發器裡Insert後,Rollback Tran 前,@@Trancount='+Rtrim(@@Trancount)
 
Rollback Tran 
 
Print N'觸發器裡Rollback Tran 後,@@Trancount='+Rtrim(@@Trancount)
 
Go

 

image

這裡,可以看到事務在觸發器中Rollback,又沒有開啟新的事務,導致整個批處理就中止,不會繼續執行觸發器外面的Rollback Tran操作。倘若,你在觸發器中使用Begin Tran …… Commit Tran格式,那麼觸發器Commit Tran不會影響到外面的事務;下面描述三種常見觸發器中事務的情況:

imageimageimage

圖5.                                                                             圖6.                                                                           圖7.

圖5.描述在觸發器中含有Begin Tran …… Commit Tran的情況,

圖6.描述在觸發器中含有Save Tran savepoint_name …… Rollback Tran savepoint_name 的情況,觸發器中的Rollback Tran 只會回滾指定的保存點,不會影響到觸發器外面的Commit Tran Or Rollback Tran操作。

圖7.描述在觸發器中含有Rollback Tran的情況,不管觸發器裡面有沒有Begin Tran,都會出現錯誤3609,中止批處理。

    注:DDL觸發器操作可以觸發器中回滾操作,可以使用命令如Rollback,但嚴重錯誤可能會導致整個事務自動回滾。不能回滾發生在 DDL 觸發器正文內的 Alter Database事件。在觸發器中使用Rollback … Begin Tran 可能會導致意想不到的結果,在沒有確認和測試情況下,請不要隨便在觸發器中直接使用Rollback …Begin Tran處理方式.特別是Create Database事件,在SQL Server 2008和SQL Server 2005環境下,產生的結果不同。

Rollback …Begin Tran情況:

Create Trigger ….

As

……

Rollback

Begin Tran

End

小結


 

     回顧前文至後文,從After觸發器VsInstead Of 觸發器,說到DML觸發器 Vs DDL觸發器,再到觸發器中事務的故事。也許有些地方描述的有些模糊,有些地方只有一筆帶過;你在測試代碼過程中,可能發現有些地方與這裡測試的情況不同,那可能是因為SQL Server版本的不同,導致一些測試結果不同。無論如何,只要你感覺對你了解觸發器,有些幫助,就OK了。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved