瞭解SQL Server觸發器及觸發器中的事務

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

  • 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觸發器的執行前後順序。    imageimage  左邊的圖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:app

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語句後的狀況。spa

image  本例子,只看到引起Check約束衝突的錯誤,而沒法看到Inserted表的數據,說明一點就是,引發Check約束以前,不會引起After觸發器tr_Contact的操做。這就驗證了圖1的After觸發器執行順序狀況。3d

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

  先修改觸發器tr_Contact內容:orm

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執行後的提示信息: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.能夠知道,在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觸發器的做用域取決於事件。下面簡單描述下事件組的內容。

  數據庫層事件主要包含:

  1. DDL Table events: Create table, Alter table, Drop table
  2. DDL view events : Create view, Alter view, Drop view
  3. DDL trigger events :Create trigger, Drop trigger, Alter trigger
  4. DDL synonym events: Create synonym, drop synonym
  5. DDL Index events: Create index, Alter index, Drop Index
  6. 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
  7. 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

  服務器層事件主要包含:

  1. Create Database, Drop Database
  2. 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

  在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. 描述在觸發器中含有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了

相關文章
相關標籤/搜索