在下面的內容,用到一些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觸發器的執行前後順序。 ![image image](http://static.javashuo.com/static/loading.gif)
左邊的圖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
本例子,只看到引起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
這裏,看到,先是觸發器操做,再是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
![image image](http://static.javashuo.com/static/loading.gif)
能夠看到,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 image](http://static.javashuo.com/static/loading.gif)
從圖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
返回錯誤信息:
使用上邊相同的方法,咱們驗證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
測試結果:
![image image](http://static.javashuo.com/static/loading.gif)
從上邊的測試狀況,看出,Update Contact觸發tr_Contact觸發器操做,觸發器裏面的Rollback Tran 動做致使了觸發器外面的Update語句執行回滾,而Rollback Tran 語句後面的Begin Tran語句,主要是應用於保持整個事務的完整性。爲了更能理解這一過程,我模擬了一個觸發器中的事務開始結束過程。
![image image](http://static.javashuo.com/static/loading.gif)
在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
![image image](http://static.javashuo.com/static/loading.gif)
在觸發器裏面沒有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 image](http://static.javashuo.com/static/loading.gif)
這裏,你是否發現一個頗有意思的問題,在觸發器理,執行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
這裏,能夠看到事務在觸發器中Rollback,又沒有開啓新的事務,致使整個批處理就停止,不會繼續執行觸發器外面的Rollback Tran操做。假若,你在觸發器中使用Begin Tran …… Commit Tran格式,那麼觸發器Commit Tran不會影響到外面的事務;下面描述三種常見觸發器中事務的狀況:
![image image](http://static.javashuo.com/static/loading.gif)
![image image](http://static.javashuo.com/static/loading.gif)
![image image](http://static.javashuo.com/static/loading.gif)
圖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了