數據庫設計(7/9):觸發器

對於設計和建立數據庫徹底是個新手?不要緊,Joe Celko, 世界上讀者數量最多的SQL做者之一,會告訴你這些基礎。和往常同樣,即便是最專業的數據庫老手,也會給他們帶來驚喜。Joe是DMBS雜誌是多年來最受 讀者喜好的做者。他在美國、英國,北歐,南美及非洲傳授SQL知識。他在ANSI / ISO SQL標準委員會工做了10年,爲SQL-89和SQL-92標準作出了傑出貢獻。算法


在第1篇到第4篇,咱們建立了表,架構的基礎和可視化。第5篇和第6篇講了存儲過程。這篇文章會講述在觸發器上,你須要儘可能避免的特性。sql

一般你會被告知,觸發器是存儲過程的「特殊類別」,但這不徹底對。它沒有參數,你不能觸發它,它有其它地方不存在本地僞表(local pseudo-tables)。在咱們詳細討論前,咱們停下來,討論下在基礎表裏,修改數據的SQL模型。數據庫

INSERT,UPDATE和DELETE

SQL是面向集合語言,所以它用數據集合同時修改表。它不是面向記錄語言,它會一次修改一條記錄來處理數據,按順序來(想下磁帶和打孔卡)。修改表內容的3個基本操做是INSERT,UPDATE和DELETE。安全

插入是它們最容易理解的。這是概念上的模型,不是實際的實現。你把行集合放入現存表。但比這更多。這個集合一次做爲整個單元,所以用CURRENT_TIMESTAMP這樣常量的任何函數調用,對於全部的新行有一樣的值。標識(IDENTITY)表性質(性質,不是列),當在增長表級別計數器的時候,經過查詢行和嘗試寫入違反了這個模型。IDENTITY值不肯定性,取決於在插入時索引和 機械裝置的物理狀態。
服務器

在ANSI/ISO標準和T-SQL的INSERT裏,新行保存在僞表裏,命名爲新。若是插入會把表引入違反任何約束的狀態,那麼事務會被系統回滾,你會收到錯誤信息。那就是說新行毫不會插入到表(可是,標識屬性會增長)。網絡

刪除是相似的。在ANSI/ISO標準和T-SQL裏DELETE裏,在基表裏符合DELETE FROM語句的WHERE字句的全部行,放入僞表裏,命名爲舊。若是刪除會把表引入違法任何約束的狀態,那麼事務會被系統回滾,你會收到錯誤信息。那就是說舊行毫不離開表。架構

實際上,大多數SQL引擎會在一次經過裏標記舊行,在最後經過裏檢查約束並移除它們。函數

更新是刪除和插入的組合。概念模型是咱們在WHERE子句上查找並建立刪除僞表,就像一個DELETE FROM。而後咱們看SET子句上建立新的要插入的僞表。工具

表由行組成,行由列組成。在SET子句裏每一個賦值同時完成。若是一列從SET子句裏丟失,SQL引擎實際上建立一個「SET <列名>=<列名>"--什麼也不作的賦值。這不像過程化語言從左到右處理。這個語句會在列a和列b裏交換值,且一次完成。 sqlserver

UPDATE Foobar
SET a = b,
     b = a;

這個過程語句會把列a和列b設置爲一樣的值,由於它從左到右執行。

 BEGIN
 SET a = b;
 SET b = a;
 END;

刪除行會被移除,插入行在表裏持續,全部都一次完成。若是更新會把表引入違反任何約束的狀態,那麼事務會被系統回滾,你會收到錯誤信息。那就是說表毫不修改。

數據庫事件

觸發器是附加到一個且只有一個表的代碼。但一個表能夠有多個觸發器。有CREATE TRIGGER語句,由於它是持續架構對象。基本的T-SQL語法很是直接:

CREATE TRIGGER <trigger name>
ON <table or view name>
{FOR | AFTER | INSTEAD OF} {[INSERT] [,] [UPDATE] [,] [DELETE]}
AS
<trigger body>;

trigger name和table name或view name就是對應的對象名稱。AFTER和FOR是等價的關鍵字,但AFTER更有描述行(其它SQL會BEFORE觸發器)。稍後咱們會講解INSTEAD OF選項。

INSERT,UPDATE,DELETE被稱爲數據庫事件或動做。SELECT語句,DROP和ALTER不是數據庫事件。當在表上這些操做中的一個完成,在數據庫動做成功完成後,觸發器會被觸發。對於每一個數據庫事件,觸發器主體裏的代碼在表層級上一次執行。

注意。你的程序更新表T1,它觸發觸發器TR1,它更新表T1自己。由於表T1被更新,觸發器TR1再次觸發,如此循壞。可能毫無休止。但它不須要在那裏中止。你的觸發器能夠引發在其它表上觸發器觸發。觸發器也能夠內嵌的。假設程序更新表T1,觸發了觸發器TR1,它更新了表T2。在T2上的觸發器觸發,再次更新表T1。這個模式能夠擴展到多個表,不中止循環。

SQL引擎須要跟蹤全部這些修改,這樣的話若是有東西出錯的話,它能夠進行回滾。避免寫這樣的代碼:它耗資源,運行緩慢且會鎖掉整個數據庫。它也很難維護。

觸發器主體

觸發器主體是一塊T-SQL過程化語句塊。但有一點區別。你不能穿參數給觸發器主體,像一個存儲過程。觸發器主體會訪問INSERTED和DELETED僞表。若是你想要的話,能夠重命名這些僞表。

這個格式也有特殊的邏輯功能:UPDATE(<列名>)和COLUMNS_UPDATED()。這些測試來看一個UPDATE FROM或INSERT INTO語句在他們的參數列表裏是否修改一個或多個列。這是專有功能,所以接下來我會詳細講解,一個簡答的例子會是:

CREATE TRIGGER No_Embezzlement_Trigger
ON Payroll
AFTER UPDATE
AS
IF UPDATE(payroll_amt)
BEGIN
RAISEERROR ('You cannot give yourself a raise');
ROLLBACK TRANSACTION;
END;

INSTEAD OF觸發器

更新視圖被熟知爲一個NP徹底問題。在英語裏,那是說,咱們知道在合理的次數裏沒有常規的方式來完成它,就如問題變得愈來愈大。

INSTEAD OF觸發器是咱們更新視圖並回避RDBMS的算法限制。一個NSTEAD OF觸發器執行觸發器主體,不是它觸發的數據庫動做。這用例子來解釋更容易。假設咱們有以在兩個表上有join和聚合函數定義的視圖:

CREATE VIEW SalesSummary (order_nbr, order_amt_tot)
AS
SELECT O.order_nbr, O.customer_name, SUM(D.unit_price * D.order_qty)
  FROM Orders AS O, Order_Details AS D
 WHERE O.order_nbr = D.order_nbr
 GROUP BY O.order_nbr;

若是對SalesSummary數據庫事件有個觸發器,不使用INSTEAD OF觸發器的話它會失敗。這個視圖有join,計算和聚合來確保它是不可更新的。但INSTEAD OF觸發器不是一個前觸發器!真正的前觸發器會執行它的代碼,而後嘗試完成數據庫事件。INSTEAD OF觸發器會獨自執行它的代碼並完成。

記住視圖是虛擬的:它物理且持續不存在。那就是說你沒有已刪除和已插入的僞表來使用。再進一步,你不能從INSERT INTO,DELETE FROM或UPDATE語句裏拿到參數。全部代碼須要在基礎表上操做。

INSTEAD OF觸發器也能夠和視圖同樣運行在基礎表上。一般它不這樣用。T-SQL習慣上進行AFTER觸發器,檢查下結果和修正,或者若是有問題的話回滾。

審計觸發器

使用觸發器的一個常見技巧是在一個或多個表裏收集審計數據。一般認爲這不是個好主意。它經過增長額外的讀寫下降了應用的性能。對於SELECT語句沒有觸發器,所以你不能跟蹤誰在查看數據。健康保險攜帶和責任法案(HIPAA (Health Insurance Portability and Accountability Act))和許多其它法律須要對每一個查看的數據都有記錄。

但不止這些,審計的基本原則是審計從被審計的事物分離。例如,當一個採購訂單建立了一個貨運,負責運輸的人和贊成這份訂單的不是同一我的。發運到你本人的誘惑避免這個方法。

假設表上有列用戶事件日期和僱員更新記錄的用戶id,這由觸發器來完成。直到你考慮到它,這才聽起來不錯。觸發器一直在,不會被推翻。哎呀!若是你刪除了行,審計數據也一塊兒刪掉。若是有人能夠訪問審計列,它們會被更新爲任何值。

第三方審計工具使用事務日誌文件,它是服務器爲恢復和用來捕獲全部須要審計的動做的網絡帶寬建立,來保持數據安全,物理上從剩下的數據庫分離。這會經過法律測試。記住ROI在今天的美國裏,表示」監禁的危險(Risk of Incarceration)「,不是」投資回報率(return on investment)「。

數據和引用完整性觸發器

若是你有舊的SQL代碼遷移到新發布的SQL,你能夠看下是否有觸發器能夠用DRI(引用完整性)約束和動做來替換。這是觸發器的初衷。例如,在訂單表裏一個訂單被刪除,觸發器會刪除訂單明細表裏的全部相關記錄。能夠問下前輩,當咱們忘記觸發器或用錯它們,花了多少時間來找無關聯的行。

如今,這些完整性觸發器的大部分能夠用聲明DRI操做來代替。它們對DELETE和UPDATE動做進行簡單的動做。這個動做是在DDL上的選項子句。完整語法是:

FOREIGN KEY (<referencing table column list>)
 REFERENCES <referenced table name> (<referenced table column list>)
[ON UPDATE | ON DELETE][NO ACTION | CASCADE | SET NULL | SET DEFAULT]

NO ACTION:一個錯誤信息告訴用戶這個操做不容許,咱們獲得一個回滾。

CASCADE:在外鍵關係裏刪除或更新全部涉及到行數據。

SET NULL:設置引用列爲NULL。這假設對於表,全部外鍵列容許接受NULL。

SET DEFAULT:這是引用表列爲定義的默認值。這假設對於表,全部列有定義的默認值。

只有在完整性規則複雜的時候才使用觸發器。一個我能想到的,當一個成員插入或刪除時,在多個組涉及值的重發布的例子。即便那樣,考慮把它放入存儲過程。

T-SQL對DDL觸發器也有擴展。這些被DDL事件觸發,而不是DML事件——CREATE, ALTER, DROP, GRANT, DENY, REVOKE 或 UPDATE STATISTICS語句

同個事件的多個觸發器

對於同個數據庫事件有不止一個觸發器是合法的。這不是個好主意,但在T-SQL裏是合法的。嘗試在一個觸發器裏完成,這樣的話容易維護。

默認狀況下,在SQL Server表裏,對於同個操做的多個觸發器是不肯定的。可是,對於兩個AFTER觸發器使用系統存儲過程settriggerorder聲明觸發順序仍是可能的。這個存儲過程不能用在INSTEAD OF觸發器。

語法很是直接:

EXEC sp_settriggerorder
@triggername = <explains itself>,
@order = [FIRST|LAST|NONE], -- firing order
@stmttype = [INSERT|UPDATE|DELETE], --trigger type
@namespace = [DATABASE|SERVER|NULL] ; -- explains itself

參數@order表示觸發起是否第一個仍是最有一個觸發。若是指定NONE,那麼沒有強制順序,咱們回到了默認的狀態。顯然你不能有2個FIRST或2個LAST觸發器。

可是,若是你有第3個觸發器,它不是FIRST,也不是LAST,那它確定在觸發順序裏的中間。

小結

各位,不止T-SQL,有觸發器的專有實現。所以它們不遷移。它們行爲的一些能夠是非肯定性的,所以它們很難調試。它們不告訴優化器它可使用的任何東西,像DRI動做作的。但是你頗有可能會發現,它們是確認複雜數據完整行規則的最安全方法。偶第神啊!

原文連接

http://www.sqlservercentral.com/articles/Stairway+Series/71822/

相關文章
相關標籤/搜索