2025年9月25日: PostgreSQL 18 釋出!
支援的版本: 當前 (18) / 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觸發器每次需要修改檢視中的一行時被觸發。觸發器函式負責執行對檢視底層基表(s)的必要修改,並在適當的情況下返回修改後的行在檢視中的樣子。檢視上的觸發器也可以定義為每次為SQL語句執行,在INSERTUPDATEDELETE操作之前或之後執行。但是,這種觸發器僅在檢視上存在INSTEAD OF觸發器時才觸發。否則,任何針對檢視的語句都必須重寫為影響其底層基表(s)的語句,然後觸發的是附加到基表(s)上的觸發器。

觸發器函式必須在建立觸發器本身之前定義。觸發器函式必須宣告為一個不接受引數並返回trigger型別的函式。(觸發器函式透過一個特殊傳遞的TriggerData結構接收其輸入,而不是以普通函式引數的形式。)

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

PostgreSQL同時提供逐行觸發器和按語句觸發器。對於逐行觸發器,觸發器函式在觸發語句影響的每一行上呼叫一次。相比之下,按語句觸發器僅在執行適用的語句時呼叫一次,而不管該語句影響的行數。特別是,一個不影響任何行的語句仍然會導致任何適用的按語句觸發器被執行。這兩種型別的觸發器有時分別被稱為行級觸發器和語句級觸發器。對TRUNCATE的觸發器只能定義在語句級別,不能逐行定義。

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

如果AFTER觸發器被定義為約束觸發器,它的執行可以推遲到事務結束,而不是語句結束。在所有情況下,觸發器都作為觸發它的語句的同一事務的一部分執行,因此如果語句或觸發器導致錯誤,兩者的效果都將被回滾。此外,觸發器將始終以觸發事件的角色執行,除非觸發器函式被標記為SECURITY DEFINER,在這種情況下,它將以函式所有者的角色執行。

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

請注意,即使沒有影響任何行(並且無論是否採取了替代的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單獨定義觸發器。相反,語句級或行級UPDATEDELETEINSERT觸發器會根據(對於語句級觸發器)MERGE查詢中指定的動作以及(對於行級觸發器)執行的動作來觸發。

在執行MERGE命令時,語句級BEFOREAFTER觸發器會為MERGE命令動作中指定的事件觸發,而不管該動作最終是否被執行。這與不更新任何行的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觸發器中對生成列值的更改將被忽略並被覆蓋。虛擬生成的列在觸發器觸發時從不計算。在C語言介面中,它們的內容在觸發器函式中是未定義的。更高級別的程式語言應防止在觸發器中訪問虛擬生成的列。

如果對同一關係上的同一事件定義了多個觸發器,則觸發器將按觸發器名稱的字母順序觸發。對於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觸發器在其之後觸發。如果你沒有特別的原因將觸發器設定為BEFOREAFTERBEFORE情況效率更高,因為操作資訊不必儲存到語句結束。

如果觸發器函式執行SQL命令,這些命令可能會再次觸發觸發器。這被稱為級聯觸發器。級聯級別沒有直接限制。級聯可能導致同一個觸發器遞迴呼叫;例如,一個INSERT觸發器可能會執行一個命令,將一個額外的行插入到同一個表中,從而再次觸發INSERT觸發器。避免這種情況下的無限遞迴是觸發器程式設計師的責任。

如果外部索引鍵約束指定了引用操作(即,級聯更新或刪除),這些操作是透過在引用表上執行常規的SQL UPDATEDELETE命令來完成的。特別是,引用表上存在的任何觸發器都會為這些更改而被觸發。如果這樣的觸發器修改或阻止了這些命令的效果,最終結果可能是破壞引用完整性。避免這種情況是觸發器程式設計師的責任。

在定義觸發器時,可以為其指定引數。在觸發器定義中包含引數的目的是允許具有相似要求的不同觸發器呼叫同一個函式。例如,可以有一個通用的觸發器函式,它將兩個列名作為引數,並將當前使用者放入其中一個,將當前時間戳放入另一個。正確編寫後,此觸發器函式將獨立於它觸發的特定表。因此,同一個函式可以用於任何具有合適列的表的INSERT事件,例如自動跟蹤事務表中記錄的建立。如果定義為UPDATE觸發器,也可以用於跟蹤最後更新事件。

支援觸發器的每種程式語言都有其自己的方法來使觸發器輸入資料可供觸發器函式使用。這些輸入資料包括觸發器事件的型別(例如,INSERTUPDATE)以及CREATE TRIGGER中列出的任何引數。對於行級觸發器,輸入資料還包括INSERTUPDATE觸發器的NEW行,以及/或UPDATEDELETE觸發器的OLD行。

預設情況下,語句級觸發器沒有辦法檢查語句修改的單個行。但是AFTER STATEMENT觸發器可以請求建立過渡表,以便使受影響的行集可供觸發器使用。AFTER ROW觸發器也可以請求過渡表,以便它們可以看到表中總的變化以及它們當前正在觸發的單個行的變化。檢查過渡表的方法再次取決於使用的程式語言,但典型的方法是使過渡表表現得像只讀的臨時表,可以在觸發器函式內發出的SQL命令訪問。

提交更正

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