支援版本:目前 (17) / 16 / 15 / 14 / 13
開發版本:devel
不支援版本:12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2

37.1. 觸發器行為概述 #

觸發器是一種規範,指示資料庫在執行特定類型的操作時自動執行特定函式。 觸發器可以附加到資料表(分割或未分割)、檢視表和外部資料表。

在資料表和外部資料表上,可以定義觸發器,以便在任何 INSERTUPDATEDELETE 操作之前或之後執行,每次針對修改的列執行一次,或每次針對SQL語句執行一次。 此外,可以設定 UPDATE 觸發器,以便僅在 UPDATE 語句的 SET 子句中提及某些欄位時才觸發。 觸發器也可以針對 TRUNCATE 語句觸發。 如果發生觸發器事件,則會在適當的時間呼叫觸發器的函式來處理該事件。

在檢視表上,可以定義觸發器,以便代替 INSERTUPDATEDELETE 操作執行。 此類 INSTEAD OF 觸發器會針對需要在檢視表中修改的每一列觸發一次。 觸發器的函式有責任對檢視表的底層基底資料表執行必要的修改,並在適當情況下傳回修改後的列,使其顯示在檢視表中。 檢視表上的觸發器也可以定義為在每次SQL語句之前或之後執行一次,針對 INSERTUPDATEDELETE 操作。 但是,僅當檢視表上也存在 INSTEAD OF 觸發器時,才會觸發此類觸發器。 否則,任何針對檢視表的語句都必須重寫為影響其底層基底資料表的語句,然後將觸發的觸發器附加到基底資料表。

必須先定義觸發器函式,才能建立觸發器本身。 觸發器函式必須宣告為不帶參數且傳回類型 trigger 的函式。(觸發器函式透過特殊傳遞的 TriggerData 結構接收其輸入,而不是以普通函式引數的形式。)

建立適合的觸發器函式後,可以使用 CREATE TRIGGER 建立觸發器。 同一個觸發器函式可用於多個觸發器。

PostgreSQL 提供逐列觸發器和逐語句觸發器。 使用逐列觸發器時,對於每個受觸發觸發器的語句影響的列,會呼叫觸發器函式一次。 相反地,無論受該語句影響的列數為何,逐語句觸發器僅在執行適當的語句時呼叫一次。 特別是,影響零列的語句仍將導致執行任何適用的逐語句觸發器。 這兩種觸發器有時分別稱為列級別觸發器和語句級別觸發器。TRUNCATE 上的觸發器只能在語句級別定義,而不能逐列定義。

觸發器也根據它們是在操作之前之後還是代替操作觸發來分類。 這些分別稱為 BEFORE 觸發器、AFTER 觸發器和 INSTEAD OF 觸發器。 語句級別的 BEFORE 觸發器自然會在語句開始執行任何操作之前觸發,而語句級別的 AFTER 觸發器則在語句的末尾觸發。 可以在資料表、檢視表或外部資料表上定義這些類型的觸發器。 列級別的 BEFORE 觸發器會在操作特定列之前立即觸發,而列級別的 AFTER 觸發器會在語句的末尾觸發(但在任何語句級別的 AFTER 觸發器之前)。 這些類型的觸發器只能在資料表和外部資料表上定義,而不能在檢視表上定義。 INSTEAD OF 觸發器只能在檢視表上定義,並且只能在列級別定義;它們會在檢視表中的每一列被識別為需要操作時立即觸發。

如果將 AFTER 觸發器定義為約束觸發器,則它的執行可以延遲到交易結束,而不是語句結束。在任何情況下,觸發器都是作為觸發它的語句的同一交易的一部分來執行的,因此如果語句或觸發器導致錯誤,兩者的效果都將被回滾。

如果 INSERT 包含 ON CONFLICT DO UPDATE 子句,則可能會在觸發的列上執行列級別的 BEFORE INSERT 觸發器,然後再執行 BEFORE UPDATE 觸發器。 如果觸發器不是等冪的,則此類交互可能很複雜,因為 BEFORE INSERT 觸發器所做的更改將被 BEFORE UPDATE 觸發器看到,包括對 EXCLUDED 列的更改。

請注意,無論 UPDATE 是否影響任何列(也無論是否採用了替代的 UPDATE 路徑),指定 ON CONFLICT DO UPDATE 時,都會執行語句級別的 UPDATE 觸發器。 具有 ON CONFLICT DO UPDATE 子句的 INSERT 將首先執行語句級別的 BEFORE INSERT 觸發器,然後執行語句級別的 BEFORE UPDATE 觸發器,接著是語句級別的 AFTER UPDATE 觸發器,最後是語句級別的 AFTER INSERT 觸發器。

針對繼承或分割階層中的父表的語句不會觸發受影響的子表的語句級別觸發器; 僅觸發父表的語句級別觸發器。 但是,任何受影響的子表的列級別觸發器都會被觸發。

如果分割表上的 UPDATE 導致列移動到另一個分割區,則它將作為從原始分割區的 DELETE,然後 INSERT 到新分割區來執行。 在這種情況下,所有列級別的 BEFORE UPDATE 觸發器和所有列級別的 BEFORE DELETE 觸發器都會在原始分割區上觸發。 然後,所有列級別的 BEFORE INSERT 觸發器都會在目標分割區上觸發。 當所有這些觸發器都影響要移動的列時,應考慮可能發生的意外結果。 關於 AFTER ROW 觸發器,將套用 AFTER DELETEAFTER INSERT 觸發器; 但是不套用 AFTER UPDATE 觸發器,因為 UPDATE 已轉換為 DELETEINSERT。 關於語句級別的觸發器,即使發生了列移動,也不會觸發任何 DELETEINSERT 觸發器; 僅會觸發在 UPDATE 語句中使用的目標表上定義的 UPDATE 觸發器。

沒有為 MERGE 定義單獨的觸發器。 相反,根據(對於語句級別的觸發器)MERGE 查詢的動作中指定了哪些動作,以及(對於列級別的觸發器)執行了哪些動作,來觸發語句級別或列級別的 UPDATEDELETEINSERT 觸發器。

在執行 MERGE 命令時,會針對 MERGE 命令的動作中指定的事件觸發語句級別的 BEFOREAFTER 觸發器,無論最終是否執行該動作。 這與 UPDATE 語句更新零列,但仍然觸發語句級別的觸發器相同。 僅當實際更新、插入或刪除列時,才會觸發列級別的觸發器。 因此,對於某些類型的動作觸發了語句級別的觸發器,而對於相同類型的動作卻沒有觸發任何列級別的觸發器,這完全是合法的。

由每個語句觸發器調用的觸發器函數應始終返回 NULL。 由每個列觸發器調用的觸發器函數可以選擇返回一個表列(類型為 HeapTuple 的值)給呼叫的執行器。 在操作之前觸發的列級別觸發器有以下選擇

  • 它可以返回 NULL 以跳過目前列的操作。 這指示執行器不要執行調用觸發器的列級別操作(特定表列的插入、修改或刪除)。

  • 僅對於列級別的 INSERTUPDATE 觸發器,返回的列將成為將被插入的列或將替換正在更新的列。 這允許觸發器函數修改正在插入或更新的列。

不打算導致這些行為之一的列級別 BEFORE 觸發器必須小心地將傳入的同一列(即,INSERTUPDATE 觸發器的 NEW 列,DELETE 觸發器的 OLD 列)作為其結果返回。

列級別的 INSTEAD OF 觸發器應返回 NULL,以指示它沒有修改檢視表的底層基本表中的任何數據,或者應返回傳入的檢視列(INSERTUPDATE 操作的 NEW 列,或 DELETE 操作的 OLD 列)。 非空的返回值用於表示觸發器在檢視表中執行了必要的數據修改。 這將導致命令影響的列數遞增。 僅對於 INSERTUPDATE 操作,觸發器可以在返回之前修改 NEW 列。 這將更改 INSERT RETURNINGUPDATE RETURNING 返回的數據,並且當檢視表不會顯示與提供的數據完全相同的數據時,這很有用。

對於在操作後觸發的列級別觸發器,將忽略返回值,因此它們可以返回 NULL

一些考量因素適用於產生的列。 儲存的產生列是在 BEFORE 觸發器之後和 AFTER 觸發器之前計算的。 因此,可以在 AFTER 觸發器中檢查產生的值。 在 BEFORE 觸發器中,正如人們所期望的那樣,OLD 列包含舊的產生值,但是 NEW 列尚未包含新的產生值,因此不應訪問它。 在 C 語言介面中,此時列的內容是未定義的; 更高階的程式語言應阻止在 BEFORE 觸發器的 NEW 列中訪問儲存的產生列。 在 BEFORE 觸發器中對產生列的值所做的變更將被忽略並覆寫。

如果為同一個關聯的同一個事件定義了多個觸發器,觸發器將按照觸發器名稱的字母順序觸發。對於 BEFOREINSTEAD OF 觸發器,每個觸發器返回的可能經過修改的列會成為下一個觸發器的輸入。如果任何 BEFOREINSTEAD OF 觸發器返回 NULL,則該列的操作將被放棄,並且後續的觸發器將不會被觸發(針對該列)。

觸發器定義還可以指定一個布林值 WHEN 條件,該條件將被測試以確定是否應觸發該觸發器。在列層級的觸發器中,WHEN 條件可以檢查列的新舊值。(語句層級的觸發器也可以具有 WHEN 條件,儘管此功能對它們來說不太有用。)在 BEFORE 觸發器中,WHEN 條件會在函數執行之前或將要執行之前進行評估,因此使用 WHEN 與在觸發器函數的開頭測試相同條件沒有實質性的區別。但是,在 AFTER 觸發器中,WHEN 條件會在列更新發生之後立即進行評估,並且它會確定是否將事件排隊以在語句結束時觸發觸發器。因此,當 AFTER 觸發器的 WHEN 條件未返回 true 時,不必將事件排隊,也不必在語句結束時重新提取列。如果觸發器只需要針對少數列觸發,則這可以顯著加速修改多個列的語句。INSTEAD OF 觸發器不支援 WHEN 條件。

通常,列層級的 BEFORE 觸發器用於檢查或修改將要插入或更新的資料。例如,可以使用 BEFORE 觸發器將目前時間插入到 timestamp 列中,或檢查列的兩個元素是否一致。列層級的 AFTER 觸發器最適用於將更新傳播到其他資料表,或針對其他資料表進行一致性檢查。這種分工的原因是 AFTER 觸發器可以確定它看到的是列的最終值,而 BEFORE 觸發器則無法確定;在其之後可能還有其他 BEFORE 觸發器會觸發。如果沒有任何特定理由將觸發器設定為 BEFOREAFTER,則 BEFORE 情況更有效率,因為關於操作的資訊不必儲存到語句結束時。

如果觸發器函數執行 SQL 命令,則這些命令可能會再次觸發觸發器。這被稱為串聯觸發器。串聯層級的數量沒有直接的限制。串聯可能會導致相同觸發器的遞迴調用;例如,INSERT 觸發器可能會執行一個命令,該命令會將額外的列插入到相同的資料表中,從而導致 INSERT 觸發器再次被觸發。避免在此類情況下出現無限遞迴是觸發器程式設計師的責任。

如果外鍵約束指定了參考動作(即串聯更新或刪除),則這些動作將透過對參考資料表的普通 SQL 更新或刪除命令來執行。特別是,參考資料表上存在的任何觸發器都將針對這些更改觸發。如果此類觸發器修改或阻止其中一個命令的效果,則最終結果可能會破壞參考完整性。避免這種情況是觸發器程式設計師的責任。

定義觸發器時,可以為其指定引數。在觸發器定義中包含引數的目的是允許具有相似要求的不同觸發器呼叫同一個函數。例如,可以有一個通用觸發器函數,該函數將兩個欄位名稱作為其引數,並將當前使用者放入一個,將目前的時間戳記放入另一個。如果編寫得當,此觸發器函數將獨立於它觸發的特定資料表。因此,相同的函數可以用於任何具有適當欄位的資料表上的 INSERT 事件,以自動追蹤交易資料表中記錄的建立。如果定義為 UPDATE 觸發器,它也可以用於追蹤上次更新事件。

支援觸發器的每種程式語言都有其自己的方法,用於將觸發器輸入資料提供給觸發器函數。此輸入資料包括觸發器事件的類型(例如,INSERTUPDATE),以及 CREATE TRIGGER 中列出的任何引數。對於列層級的觸發器,輸入資料還包括 INSERTUPDATE 觸發器的 NEW 列,以及 UPDATEDELETE 觸發器的 OLD 列。

預設情況下,語句層級的觸發器沒有任何方法可以檢查語句修改的個別列。但是,AFTER STATEMENT 觸發器可以請求建立轉換資料表,以使受影響的列集合可供觸發器使用。AFTER ROW 觸發器也可以請求轉換資料表,以便它們可以查看資料表中的總體更改以及當前正在觸發的個別列中的更改。再次檢查轉換資料表的方法取決於所使用的程式語言,但是典型的方法是使轉換資料表充當唯讀臨時資料表,這些資料表可以透過觸發器函數中發出的 SQL 命令來存取。

提交更正

如果您在文件中發現任何不正確、與您對特定功能的體驗不符或需要進一步澄清的內容,請使用此表格報告文件問題。