SQL Server 觸發器

from:http://www.cnblogs.com/kissdodog/p/3173421.html html

觸發器能夠作不少事情,但也會帶來不少問題。使用它的技巧在於在適當的時候使用,而不要在不適當的時候使用它們。sql

  觸發器的一些常見用途以下:數據庫

  • 彈性參照完整性:實現不少DRI不能實現的操做(例如,跨數據庫或服務器的參照完整性以及不少複雜的關係類型)。
  • 建立神級跟蹤:這意味寫出的記錄不只跟蹤大多數當前的數據,還包括對每一個記錄進行實際修改的歷史數據。隨着SQL Server2008中的更改數據跟蹤功能的出現,建立審計跟蹤再也不那麼流行,但之前使用的就是觸發器。
  • 執行與CHECK約束相似的功能,可是跨表,跨數據庫甚至是跨服務器使用。
  • 用本身的語句代替用戶的操做語句。

1、觸發器的概念

  觸發器是一種特殊類型的存儲過程,對特定事件做出相應。觸發器有兩種類型:數據定義語言(DDL)觸發器和數據操縱語言(DML)觸發器。服務器

  DDL觸發器在用戶以某些方式(CREATE、ALTER、DROP或類似的語句)對數據庫結構進行修改時激活做出響應。通常來講,只會在對數據庫結構的改變或歷史進行極爲嚴格的審計時纔會用到DDL觸發器。併發

  DML觸發器是一些附加在特定表或視圖上的代碼片斷。與須要顯式調用代碼的存儲過程不一樣,只要有附加觸發器的時間在表中發生,觸發器中的代碼就會自動運行。實際上也不能顯式地調用觸發器-惟一的作法是在指定的表中執行所需的操做。函數

  除了不可以顯式地調用觸發器,還可在存儲過程當中發現另外兩個觸發器所沒有的內容:參數和返回碼。性能

  可將觸發器附加到什麼事件呢?由於在SQL中可使用3類動做查詢,因此就有3種類型的觸發器,另外加上混合搭配這些時間並對時間定時激活的混合觸發器類型。測試

  • INSERT觸發器
  • DELETE觸發器
  • UPDATE觸發器
  • 之後任意類型的混合

  注意:加密

  值得注意的是,有時即便執行的動做是前面這些類型中的一種,觸發器也不會激活。問題在於進行的操做是否在記錄的活動中。例如,DELETE語句是一個正常的記錄活動,它會激活任何刪除觸發器,而TRUNCATE TABLE也有刪除行的做用,但只是把表使用的空間釋放而已-沒有記錄單個行刪除操做,因此沒有激活任何觸發器。批量操做默認狀況下不激活觸發器,須要顯式告知批量操做激活觸發器。spa

  建立觸發器的語法:

  CREATE TRIGGER <trigger name>
  ON [ <schema name>. ]<table or view name>
  [WITH ENCRYPTION | EXECUTE AS <CALLER | SELF | <user> > ]
  {{{ FOR | AFTER} < [DELETE][,][INSERT][,][UPDATE] > } | INSTEAD OF }[WITH APPEND][NOT FOR REPLICATION]
  AS
  < <sql statements> | EXTERNAL NAME <assembly method specifier> >

  ON子句用來之處觸發器將要附加的表,以及在什麼時候何種狀況下激活這個觸發器。

  一、ON子句

  這部分只是對建立觸發器所針對的對象進行命名。記住,若是觸發器的類型是AFTER觸發器(使用FOR或AFTER來聲明觸發器),那麼ON子句的目標就必須是一個表-AFTER觸發器不支持視圖。

  二、WITH ENCRYPTION選項

  加密觸發器。若是添加了這個選項,則能夠確保沒有人可以查看你的代碼(甚至是你本身)。和視圖與存儲過程同樣,使用WITH ENCRYPTION選項須要記住的是,每次在觸發器上使用ALTER語句時都必須從新應用該選項,若是使用ALTER STATEMENT語句但不包含WITH ENCRYPTION選項,那麼觸發器就再也不被加密。

  三、FOR|AFTER子句與INSTEAD OF子句

  除了要肯定激活觸發器(INSERT、UPDATE、DELETE)的查詢類型之外,還要對觸發器的激活時間作出選擇。雖然人們常常考慮使用FOR觸發器,可是也可使用INSTEAD OF觸發器。對着兩個觸發器的選擇將會影響到是在修改數據以前仍是以後進入觸發器。FOR和AFTER的意義是同樣的。

  FOR|AFTER

  FOR(或者AFTER)子句代表了指望觸發器在何種動做類型下激活。當有INSERT、UPDATE或DELETE或三者混合操做時,均可以激活觸發器。

  FOR INSERT,DELETE
  --或者是:
  FOR UPDATE,INSERT
  --或者是:
  FOR DELETE

  一、INSERT觸發器

  當有人向表中插入新的一行時,被標記爲FOR INSERT的觸發器的代碼就會執行。對於插入的每一行來講,SQL Server會建立一個新行的副本並把該副本插入到一個特殊的表中,該表只在觸發器的做用域內存在,該表被稱爲Inserted表。特別須要注意的是,Inserted表只在觸發器激活時存在。在觸發器開啓以前或完成以後,都要認爲該表示不存在的。

  二、DELETE觸發器

  它和INSERT觸發器的工做方式相同,只是Inserted表示空的(畢竟是進行刪除而非插入,因此對於Inserted表示沒有記錄)。相反,每一個被刪除的記錄的副本將會插入到另外一個表中,該表稱爲Deleted表,和Inserted表相似,該表只存在於觸發器激活的時間內。

  三、UPDATE觸發器

  除了有一點改變之外,UPDATE觸發器和前面的觸發器是很相似的。對錶中現有的記錄進行修改時,都會激活被聲明FOR UPDATE的觸發器的代碼。惟一的改變是沒有UPDATE表。SQL Server認爲每一行好像刪除了現有記錄,並插入了全新的記錄。聲明爲FOR UPDATE的觸發器並非只包含一個表,而是兩個特殊的表,稱爲Inserted表和Deleted表。固然,這兩個表的行數是徹底相同。

  四、WITH APPEND選項

  WITH APPEND選項並不經常使用,老實講,用到它的可能性很小;WITH APPEND選項只能應用於6.5兼容模式中。

  若是已經聲明瞭一個稱爲trgCheck的觸發器在更新和插入時強制執行數據完整性,那麼就不能建立另外一個觸發器來進行級聯更新。一旦建立了更新(或插入、刪除)觸發器,那麼就不能建立另外一個同一動做類型的觸發器。爲解決這個問題,WITH APPEND子句顯式地告訴SQL Server,即便在表上已經有了這種類型的觸發器,還能夠添加一個新的觸發器。當有合適的觸發動做(INSERT、UPDATE、DELETE)發生時,會同時激活兩個觸發器。

  五、NOT FOR REPLICATION選項

  若是添加了該選項,會稍微地改變關於什麼時候激活觸發器的規則。在適當的位置使用這個選項,不管與複製相關的任務什麼時候修改表,都不會激活觸發器。一般,當修改了原始表,而且不會再進行修改的時候會激活觸發器(進行內務處理或級聯等操做)。

  六、AS子句

  和在存儲過程當中的使用徹底相同,這正是觸發器的實質所在。AS關鍵字告訴SQL Server,代碼將要啓動。

2、使用觸發器實施數據完整性規則

  雖然觸發器不會成爲首要的選擇,可是觸發器也一樣能夠執行和CHECK約束甚至是DEFAULT約束同樣的功能。使用觸發器仍是CHECK約束?答案是:看狀況而定。若是CHECK約束能夠完成,那麼可能CHECK約束是更受青睞的選擇。可是,有時會出現CHECK約束不能完成任務的狀況,或是CHECK過程當中的某些固有內容使其顯得不如觸發器更爲可取。

  想要使用觸發器而非CHECK約束的例子包括:

  • 業務規則須要引用單個表中的數據。
  • 業務規則須要檢查更新的變化。
  • 須要一個定製的錯誤消息。

  一、處理來自於其餘表的需求

  CHECK約束快速並且有效,可是他們不是萬能的。可能當你須要跨表驗證時,它最大的缺點就會暴露出來。

  爲了演示一次跨表約束,本處新建兩個表用於測試:

  

  此處外鍵列是ProductId。此處咱們要測試的是,當產品表的PruductNumber(庫存,單詞不懂寫)小於等於0的時候,不容許再添加1產品的訂單。

  下面建立一個觸發器以下:

複製代碼
  CREATE TRIGGER ProductNumCheck
  ON [Order]
  FOR INSERT
  AS
  DECLARE @i int
  SELECT @i = ProductId FROM Inserted        --Inserted表示最後插入的記錄的表
  IF(SELECT ProductNumber FROM Product 
  WHERE ProductId = 
  (SELECT ProductId FROM Inserted)) <=0
  PRINT @i
  BEGIN
      PRINT '庫存不足,禁止購買!'
      ROLLBACK TRANSACTION    --回滾,避免插入
  END
複製代碼

  如今咱們來添加一個產品:

  INSERT INTO [Order] VALUES(3,2,GETDATE())

  顯示消息以下:

  

  咱們看到,當Product的庫存不足時,將不容許添加訂單。

  二、使用觸發器檢查更新的變化

  有時,你可能並不關心過去的值和如今的值,只是想知道變化的量。雖然沒有任何列或表給出這些信息,可是能夠在觸發器中使用Inserted表和Deleted表進行計算。、

  例如,剛纔的產品表,假設在下訂單時會修改產品的庫存,咱們不容許一次UPDATE Product超過10個。

複製代碼
  CREATE TRIGGER ProductNumUpdate
  ON Product
  FOR UPDATE
  AS
  IF EXISTS(SELECT * FROM Inserted AS i INNER JOIN Deleted as d ON i.ProductId = d.ProductId WHERE i.ProductNumber - d.ProductNumber > 10)
  BEGIN
      PRINT '超過10個,不容許更新';
      ROLLBACK TRANSACTION    --回滾,避免插入
  END
複製代碼

  添加超過10條的時候

  UPDATE Product SET ProductNumber = ProductNumber + 11 WHERE ProductId = 1

  顯示結果以下:

  

   添加少於10條的時候

  UPDATE Product SET ProductNumber = ProductNumber + 1 WHERE ProductId = 1

  顯示結果以下:

  

  三、將觸發器用於自定義錯誤消息

  當想要對傳給用戶或客戶端應用程序的錯誤消息或錯誤號進行控制時,使用觸發器是很方便的。

  例如,若是使用CHECK約束,只能獲得標準的547錯誤,而且沒有詳盡的解釋。一般,對於想知道具體錯誤的用戶來講,這是無用的信息-缺失,客戶端應用程序常常由於沒有足夠的信息而不能表明用戶作出只能和有幫助的響應。

  簡而言之,當已經具有了數據完整性,可是沒有足夠的信息進行處理的時候,能夠建立觸發器。

  注意:

  儘管傳遞自定義錯誤代碼頗有用,但SQL Server中對自定義錯誤消息的需求仍是相對較少。爲何不傳遞自定義錯誤消息呢?緣由在於某些用戶認爲自定義錯誤消息之上有一個應用程序層,而且可能須要更多有關錯誤的上下文信息,所以特定於SQL Server的文本就沒法充分發揮做用。而這時若是使用特定的錯誤代碼,對於應用程序則有很大幫助,有助於肯定確切發生的事件以及應用正確的客戶端錯誤處理代碼。

3、觸發器的常見用途

   一、觸發器能夠嵌套

  嵌套的觸發器是指那些不是由發出語句直接激活的,而是由另外一個觸發器發出的語句激活的觸發器。

  這實際上會引發一連串的事件,一個觸發器激活另外一個觸發器,而另外一個觸發器又激活其餘觸發器。

  觸發器能夠激活的深度取決於如下幾個因素:

  •   嵌套的觸發器是否已在系統中打開(這是系統級的而不是數據庫級的選項;可使用sp_configure來設置,默認爲打開的)。
  •   是否有嵌套的深度不超過32層。
  •   觸發器是否已經被激活。觸發器默認爲每一個觸發器事務只能被激活一次。一旦被激活,則觸發器會忽略其餘任何調用,將這些調用做爲相同觸發器動做的一部分。一旦執行一條全新的語句,處理過程就會從新開始。

  注意,若是在嵌套鏈中的任何地方進行了ROLLBACK操做,那麼整條鏈都會回滾。換句話說,整個觸發器鏈就像一個事務同樣。

   二、觸發器能夠遞歸

  什麼是遞歸觸發器?若是某觸發器所作的事情最終激活了自身,那麼該觸發器就是遞歸的。能夠直接觸發(經過設置了觸發器的表進行動做查詢來完成),也能夠間接觸發(經過嵌套過程)。

   遞歸觸發器比較少見,默認狀況下,遞歸觸發器是關閉的。遞歸是數據庫級的選項,可使用sp_dboption系統存儲過程來設置。

  遞歸觸發器的風險在於可能會陷入某種非預設的循環之中。這樣便須要確保在必要的時候能夠經過遞歸檢查的形式來中止這一過程。

  三、觸發器不能防止體系結構的修改

  觸發器有助於更容易地修改體系結構。事實上,一般在開發週期的早期使用觸發器實施參照完整性,而在後期,也就是要進入生產環境時將其改成DRI。

  四、能夠在不刪除的狀況下關閉觸發器

  有時,像CHECK約束同樣,你想要關閉完整性功能以便於執行一些違反約束可是有效的動做(最多見的就是導入數據)。

  可使用ALTER語句來關閉觸發器,語法以下:

  ALTER TABLE <table name>
    <ENABLE|DISABLE> TRIGGER <ALL|<trigger name>>

  若是關閉觸發器是爲了導入數據,那麼建議踢出全部用戶並進入單用戶模式。dbo-only模式,或同時進入兩種模式。這樣一來,當關閉觸發器時,就能確保萬無一失。

  五、觸發器的激活順序

  對於任何給定的表(只有AFTER觸發器才能夠指定激活順序),給定的視圖(只有INSTEAD OF觸發器才能夠指定激活順序)。能夠選擇一個觸發器優先激活(FIRST惟一一個)。一樣,能夠選擇一個觸發器最後激活(LAST,只能選一個)。其餘全部的觸發器之間沒有什麼優先激活順序,也就是說,除了能保證FIRST第一個觸發和LAST最後激活以外,不能保證NONE觸發器的順序。

  FIRST和LAST觸發器的建立和其餘任何觸發器的建立相同,在已經建立觸發器以後使用存儲過程sp_settriggerorder來聲明激活順序。

  sp_settriggerorder語法以下:

    sp_settriggerorder[@triggername =] '<trigger name>',
    [@order =] '{FIRST|LAST|NONE}',
    [@stmttype =] '{INSERT|UPDATE|DELETE}'
    [, [@namespace =] {'DATABASE'|'SERVER'|NULL}]

  這裏對於任何特殊操做(INSERT、UPDATE、DELETE)來講,只能有惟一的FIRST觸發器。一樣,對於任何特殊操做來講,也只能有惟一的LAST觸發器。其餘觸發器的數量能夠看作是NONE-也就是說,沒有特殊激活順序的觸發器的數量是沒有限制的。

  

  爲何要控制激活順序

  一、出於邏輯緣由而控制激活順序

  爲何要在激活一個觸發器以前去激活另外一個觸發器。最多見的理由是第一個觸發器是後面觸發器的基礎或前面的觸發器使後面的觸發器有效。

  二、處於性能緣由而控制激活順序

  在性能方面,FIRST觸發器是惟一塊兒關鍵做用的觸發器,若是有多個觸發器,可是其中只有一個觸發器可能會產生回滾,那麼就須要考慮將這個觸發器標記爲FIRST觸發器,這能令外回滾的操做更少。

4、性能考慮

  一、觸發器的被動型

  這裏的意思是指觸發器發生在事務以後。當激活觸發器時,整個查詢已經運行而且事務也已經被記錄到日誌中(但未提交,只是記錄到激活觸發器的語句點)。這意味着若是觸發器須要回滾,那麼必須撤銷已經作的全部工做。這和約束是不一樣,約束是主動的,約束是發生在語句真正執行前。這意味着約束會檢測可能失敗的操做,而且在進程的前期就予以阻止。因此約束一般運行得快一些-在更爲複雜的查詢中速度更快。注意,只有在發生回滾時,約束明顯更快。

   若是正在處理少許回滾,並且受影響的語句的複雜性較低,執行之間較短,那麼觸發器和約束之間沒有太大的區別。可是在沒法預知回滾的數量的時候,堅持使用約束的效率更好。

  二、觸發器與激活的進程之間不存在併發問題

  若是激活語句不是顯示事務的一部分,那麼該語句仍然是其自身的但語句事務的一部分。不管何種狀況,觸發器內部發出的ROLLBACK TRAN仍然會回滾整個事務。

  這種同屬一個事務的另外一個結果是觸發器繼承了他們所屬事務上已打開的鎖。這意味着不須要作任何特殊的處理來避免碰到事務中其餘語句建立的鎖。在事務的做用域內能夠自由訪問,而且能夠發現數據庫基於事務中先前的語句所做的修改。

  三、使用IF UPDATE()和COLUMNS_UPDATE()

  在UPDATE觸發器中,能夠經過檢查感興趣的列是否已被修改來限制在觸發器中執行的代碼總量。爲了實現這一點,可使用UPDATE()或COLUMN_UPDATE()函數。

  一、UPDATE()函數

  UPDATE()函數只在觸發器的做用域內適用。它惟一的目的是提供一個布爾值,來講明特殊列是否已經更新。使用這個函數能夠決定一個特定的代碼塊是否須要運行-例如該代碼只在特定列更新時才運行。

  建一張表以下:

  

  建立觸發器以下:

複製代碼
  CREATE TRIGGER UPDATECHECK
  ON tb_Money
  FOR UPDATE
  AS
  IF UPDATE(MyMoney)    --若是更新了MyMoney才觸發
  BEGIN
      PRINT('個人錢改變了!');
  END
複製代碼

  執行語句:

  UPDATE tb_Money SET MyMoney = '101' WHERE Id = 1  --改變了MyMoney激活了觸發器

  輸出以下:

  

  留意到,改變了MyMoney列,激活了觸發器。

  執行語句:

  UPDATE tb_Money SET Name = '張飛' WHERE Id = 1

  顯示結果以下:

  

  二、COLUMNS_UPDATE()函數

  這個函數和UPDATE()的運行方式不一樣,但目的相同。COLUMNS_UPDATE()函數能夠一次檢查多列。爲了實現這一點,該函數使用了位掩碼,位掩碼將varbinary數據的一個或多個字節中的單個位與表中的單個列相關聯。

  

  對於上圖的狀況,數據的單個字節說明了第2,第3,以及第6列已經更新,而其餘列沒有更新。

  對於超過8列的狀況,SQL Server就會在右邊添加另外一個字節而且繼續計數。

  

  對於上圖,此次是跟心了第2,第9以及第14列。

  這些信息怎麼使用呢?

  •   |  表示 或
  •   &   表示 與
  •   ^   表示 異或

  示例:

  COLUMN_UPDATE()>0  檢查是否有列被更新。

  COLUMN_UPDATE()^21=0  檢查是否更新了全部指定列(一、三、5)。

  仍是剛纔那張表:

  

  建立觸發器以下:

複製代碼
  CREATE TRIGGER UPDATECHECK2
  ON tb_Money
  FOR UPDATE
  AS
  IF COLUMNS_UPDATED()&7 = 3    --若是同時更新了Name,MyMoney才觸發
  BEGIN
      PRINT('個人錢和姓名改變了!');
  END
複製代碼

  執行語句以及說明以下:

複製代碼
  UPDATE tb_Money SET Name = '張飛' WHERE Id = 1

  UPDATE tb_Money SET Name = '趙雲', MyMoney = 102 WHERE Id = 1    --此行會激活觸發器
  --計算過程以下
  --Id    Name    tb_Money
  --1        1        1    7(所有更新爲7)
  --0        1        1    Name和tb_Money同時更新爲(與3=3)
複製代碼

  五、儘可能別在觸發器中回滾

  若是在觸發器中使用不少的ROLLBACK TRAN語句,那麼請確保在執行激活觸發器的語句前預先進行錯誤檢查。SQL Server在這種狀況下,是被動的,但你能夠主動。時間檢查錯誤,而不是等待回滾。

  由於回滾的代價是昂貴的。

5、刪除觸發器

  刪除觸發器和普通刪除操做略有不一樣,和表同樣,其問題在於觸發器的名稱被限定在模式級別。這意味着一個觸發器能夠有兩個名稱相同的對象,只要方式觸發器的對象與觸發器另外一個同名的對象位於不一樣的模式中。重申一次,觸發器是以其所處的模式命名的,而不是以觸發器所關聯的對象命名。

  刪除觸發器的語法以下:

  DROP TRIGGER [<schema>.]<trigger name>

  除了模式問題以外,刪除觸發器就和刪除其餘對象同樣簡單了。

相關文章
相關標籤/搜索