39.1. Обзор механизма работы триггеров #
Триггер является указанием, что база данных должна автоматически выполнить заданную функцию, всякий раз когда выполнен определённый тип операции. Триггеры можно использовать с таблицами (секционированными и обычными), с представлениями и с внешними таблицами.
Для обычных и сторонних таблиц можно определять триггеры, которые будут срабатывать до или после любой из команд INSERT
, UPDATE
или DELETE
; либо один раз для каждой модифицируемой строки, либо один раз для оператора SQL. Триггеры на UPDATE
можно установить так, чтобы они срабатывали, только когда в предложении SET
оператора UPDATE
упоминаются определённые столбцы. Также триггеры могут срабатывать для операторов TRUNCATE
. Если происходит событие триггера, для обработки этого события в установленный момент времени вызывается функция триггера.
Для представлений триггеры могут быть определены для выполнения вместо операций INSERT
, UPDATE
и DELETE
. Такие триггеры INSTEAD OF
вызываются единожды для каждой строки, которая должна быть изменена в этом представлении. Именно функция триггера отвечает за то, чтобы произвести необходимые изменения в нижележащих базовых таблицах представления и должным образом возвращать изменённые строки, чтобы они появлялись в представлении. Триггеры для представлений тоже могут быть определены так, что они будут выполняться единожды для всего оператора SQL, до или после операций INSERT
, UPDATE
или DELETE
. Однако такие триггеры срабатывают, только если для представления определён триггер INSTEAD OF
. В противном случае все операторы, обращающиеся к представлению, должны быть переписаны в виде операторов, обращающихся к нижележащим базовым таблицам, и тогда будут срабатывать триггеры, установленные для этих таблиц.
Триггерная функция должна быть создана до триггера. Она должна быть объявлена без аргументов и возвращать тип trigger
. (Триггерная функция получает данные на вход посредством специально переданной структуры TriggerData
, а не в форме обычных аргументов.)
После создания триггерной функции создаётся триггер с помощью CREATE TRIGGER. Одна и та же триггерная функция может быть использована для нескольких триггеров.
Postgres Pro предлагает как построчные, так и операторные триггеры. В случае построчного триггера триггерная функция вызывается один раз для каждой строки, затронутой оператором, запустившим триггер. Операторный же триггер, напротив, вызывается только один раз при выполнении соответствующего оператора, независимо от количества строк, которые он затрагивает. В частности оператор, который не затрагивает никаких строк, всё равно приведёт к срабатыванию операторного триггера. Эти два типа триггеров также называют триггерами уровня строк и триггерами уровня оператора, соответственно. Триггеры на 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
уровня оператора вызываются при ON CONFLICT DO UPDATE
независимо от того, будут ли изменены какие-либо строки в результате UPDATE
(и даже в случае, когда альтернативный путь UPDATE
вообще не выбирается). При выполнении запроса INSERT
с предложением ON CONFLICT DO UPDATE
сначала выполняются триггеры BEFORE
INSERT
, затем триггеры BEFORE
UPDATE
, потом триггеры AFTER
UPDATE
и, наконец, AFTER
INSERT
(речь идёт о триггерах на уровне операторов).
Оператор, нацеленный на родительскую таблицу в иерархии наследования или секционирования, не вызывает срабатывания триггеров уровня оператора для задействованных дочерних таблиц; срабатывать будут только такие триггеры для родительской таблицы. Однако если для этих дочерних таблиц установлены триггеры уровня строк, они будут срабатывать.
Если оператор UPDATE
в секционированной таблице должен переместить строку в другую секцию, это перемещение реализуется в результате выполнения DELETE
в исходной секции и последующего INSERT
в новой секции. При этом в исходной секции срабатывают все триггеры BEFORE
UPDATE
и BEFORE
DELETE
уровня строк. Затем в целевой секции срабатывают все триггеры BEFORE
INSERT
уровня строк. Следует иметь в виду, что в случаях, когда все эти триггеры модифицируют перемещаемую строку, полученный результат может быть неожиданным. Если рассматривать триггеры AFTER ROW
, то применяться будут триггеры AFTER
DELETE
и AFTER
INSERT
, но не триггеры AFTER
UPDATE
, так как команда UPDATE
заменяется на DELETE
и INSERT
. Если же рассматривать триггеры уровня операторов, ни триггеры DELETE
, ни триггеры INSERT
не будут срабатывать, даже если производится перемещение строк; сработают только триггеры UPDATE
, установленные в целевой таблице оператора UPDATE
.
Для команды MERGE
нет отдельных триггеров. Вместо этого срабатывают триггеры команд UPDATE
, DELETE
и INSERT
в зависимости от того, какие действия указаны в запросе MERGE
(триггеры уровня оператора) и какие действия выполняются (триггеры уровня строк).
При выполнении команды MERGE
срабатывают триггеры уровня оператора BEFORE
и AFTER
при событиях, указанных в действиях команды MERGE
, независимо от того, было ли действие в итоге выполнено. Это не отличается от поведения команды UPDATE
, для которой также срабатывают триггеры уровня оператора, когда строки не меняются. Триггеры уровня строк срабатывают только при фактическом добавлении, изменении или удалении строки. Таким образом, абсолютно нормально, когда для определённых действий срабатывают триггеры уровня оператора и при этом не срабатывают триггеры уровня строк.
Триггерные функции, вызываемые триггерами операторов, должны всегда возвращать NULL
. Триггерные функции, вызываемые триггерами строк, могут вернуть строку таблицы (значение типа HeapTuple
). У триггера уровня строки, срабатывающего до операции, есть следующий выбор:
Можно вернуть
NULL
, чтобы пропустить операцию для текущей строки. Это указывает исполнителю запросов, что не нужно выполнять операцию со строкой вызвавшей триггер (вставку, изменение или удаление конкретной строки в таблице).Возвращаемая строка для триггеров
INSERT
илиUPDATE
будет именно той, которая будет вставлена или обновлена в таблице. Это позволяет триггерной функции изменять вставляемую или обновляемую строку.
Если в триггере BEFORE
уровня строки не планируется использовать любой из этих вариантов, то нужно аккуратно вернуть в качестве результата ту же строку, которая была передана на вход (то есть строку NEW
для триггеров INSERT
и UPDATE
, или строку OLD
для триггеров DELETE
).
Триггер уровня строки INSTEAD OF
должен вернуть либо NULL
, чтобы указать, что он не модифицирует базовые таблицы представления, либо он должен вернуть строку представления, полученную на входе (строку NEW
для операций INSERT
и UPDATE
или строку OLD
для операций DELETE
). Отличное от NULL
возвращаемое значение сигнализирует, что триггер выполнил необходимые изменения данных в представлении. Это приведёт к увеличению счётчика количества строк, затронутых командой. Для операций INSERT
и UPDATE
(и только для них) триггер может изменить строку NEW
перед тем как её вернуть. В результате будут изменены данные, возвращаемые INSERT RETURNING
или UPDATE RETURNING
, что полезно, когда представление должно возвращать не те данные, что были получены.
Возвращаемое значение игнорируется для триггеров уровня строки, вызываемых после операции, поэтому они могут возвращать NULL
.
Генерируемые столбцы заслуживают отдельного внимания. Сохраняемые генерируемые столбцы вычисляются после триггеров BEFORE
и перед триггерами AFTER
. Таким образом, в триггерах AFTER
можно наблюдать сгенерированное значение. В триггерах BEFORE
строка OLD
, как можно было ожидать, содержит предыдущее значение, однако в строке NEW
ещё не содержится новое сгенерированное значение, и обращаться к нему не следует. На уровне языка C содержимое столбца в этот момент считается неопределённым; более высокоуровневые языки должны блокировать обращения к сохраняемому генерируемому столбцу в строке NEW
внутри триггера BEFORE
. Изменённые в триггере BEFORE
значения генерируемого столбца игнорируются и будут перезаписаны.
Если есть несколько триггеров на одно и то же событие для одной и той же таблицы, то они будут вызываться в алфавитном порядке по имени триггера. Для триггеров BEFORE
и INSTEAD OF
потенциально изменённая строка, возвращаемая одним триггером, становится входящей строкой для следующего триггера. Если любой из триггеров BEFORE
или INSTEAD OF
возвращает NULL
, операция для этой строки прекращается и последующие триггеры (для этой строки) не срабатывают.
В определении триггера можно указать логическое условие WHEN
, которое будет проверяться, чтобы посмотреть, нужно ли запускать триггер. В триггерах уровня строки в условии WHEN
можно проверять старые и/или новые значения столбцов строки. (В триггерах уровня оператора также можно использовать условие WHEN
, хотя в этом случае это не так полезно.) В триггерах BEFORE
условие WHEN
вычисляется непосредственно перед тем, как триггерная функция будет выполнена, поэтому использование WHEN
существенно не отличается от выполнения той же проверки в самом начале триггерной функции. Однако в триггерах AFTER
условие WHEN
вычисляется сразу после обновления строки и от этого зависит, будет ли поставлено в очередь событие запуска триггера в конце оператора или нет. Поэтому, когда условие WHEN
в триггере AFTER
не возвращает истину, не требуется ни постановка события в очередь, ни повторная выборка этой строки в конце оператора. Это может существенно ускорить работу операторов, изменяющих большое количество строк, с триггером, который должен сработать только для нескольких. В триггерах INSTEAD OF
не поддерживается использование условий WHEN
.
Как правило, триггеры BEFORE
уровня строки используются для проверки или модификации данных, которые будут вставлены или изменены. Например, триггер BEFORE
можно использовать для вставки текущего времени в столбец timestamp
или проверки, что два элемента строки согласованы между собой. Триггеры AFTER
уровня строки наиболее разумно использовать для каскадного обновления данных в других таблицах или проверки согласованности сделанных изменений с данными в других таблицах. Причина для такого разделения работы в том, что триггер AFTER
видит окончательное значение строки, в то время как для триггера BEFORE
это не так, ведь могут быть другие триггеры BEFORE
, которые сработают позже. Если нет особых причин для выбора между триггерами BEFORE
или AFTER
, то триггер BEFORE
предпочтительнее, так как не требует сохранения информации об операции до конца работы оператора.
Если триггерная функция выполняет команды SQL, эти команды могут заново запускать триггеры. Это известно как каскадные триггеры. Прямых ограничений на количество каскадных уровней не существует. Вполне возможно, что каскадные вызовы приведут к рекурсивному срабатыванию одного и того же триггера. Например, в триггере INSERT
может выполняться команда, которая добавляет строку в эту же таблицу, тем самым опять вызывая триггер на INSERT
. Обязанность программиста не допускать бесконечную рекурсию в таких случаях.
Если в ограничении внешнего ключа указаны ссылочные действия (а именно каскадное изменение или удаление данных), они выполняются обычными SQL-командами UPDATE
или DELETE
в отношении целевой таблицы. В частности, на эти изменения сработают все существующие для целевой таблицы триггеры. Если от такого триггера меняется или блокируется действие этих команд, это в конечном счёте может привести к нарушению ссылочной целостности. При разработке триггеров следует этого избегать.
При определении триггера можно указывать аргументы. Цель включения аргументов в определение триггера в том, чтобы позволить разным триггерам с аналогичными требованиями вызывать одну и ту же функцию. В качестве примера можно создать обобщенную триггерную функцию, которая принимает два аргумента с именами столбцов и записывает текущего пользователя в первый аргумент и текущий штамп времени во второй. При правильном написании такая триггерная функция будет независима от конкретной таблицы, для которой она будет запускаться. Таким образом, одна и та же функция может использоваться при выполнении INSERT
в любую таблицу с соответствующими столбцами, чтобы, например, автоматически отслеживать создание записей в транзакционной таблице. Для триггеров UPDATE
аргументы также могут использоваться для отслеживания последних сделанных изменений.
У каждого языка программирования, поддерживающего триггеры, есть свой собственный метод доступа из триггерной функции к входным данным триггера. Входные данные триггера включают в себя тип события (например, INSERT
или UPDATE
), а также любые аргументы, перечисленные в CREATE TRIGGER
. Для триггеров уровня строки входные данные также включают строку NEW
для триггеров INSERT
и UPDATE
и/или строку OLD
для триггеров UPDATE
и DELETE
.
Триггеры уровня оператора по умолчанию не имеют возможностей для проверки отдельных строк, модифицированных оператором. Но триггер AFTER STATEMENT
может запросить создание для него переходных таблиц, чтобы ему были доступны наборы затрагиваемых операцией строк. Триггерам AFTER ROW
также могут предоставляться переходные таблицы, чтобы они могли видеть все изменения в таблице, а не только изменения в отдельных строках, для которых они срабатывают. Метод обращения к переходным таблицам определяется применяемым языком программирования, но обычно переходные таблицы представляются как временные таблицы только для чтения, к которым в триггерной функции можно обращаться, выполняя SQL-команды.