Выполнение триггера ПОСЛЕ завершения транзакции

В PostgreSQL запускаются триггеры DEFERRED до (внутри) завершения транзакции или сразу после нее?

В документации говорится:

DEFERRABLE
NOT DEFERRABLE

Это определяет, можно ли отложить ограничение. Ограничение то есть не откладывается, будет проверяться сразу после каждого команда. Проверка ограничений, которые откладываются, может быть отложена до конца транзакции (используя команду SET CONSTRAINTS).

Он не указывает, находится ли он внутри транзакции или отсутствует. Мой личный опыт говорит, что он находится внутри транзакции, и мне нужно, чтобы он был снаружи!

Выполняются ли три транзакции DEFERRED (или INITIALLY DEFERRED) внутри транзакции? И если да, то как я могу отложить их выполнение до момента завершения транзакции?

Чтобы дать вам подсказку, что мне нужно, я использую pg_notify и RabbitMQ (PostgreSQL LISTEN Exchange) для отправки Сообщения. Я обрабатываю такие сообщения во внешнем приложении. Прямо сейчас у меня есть триггер, который уведомляет внешнее приложение вновь вставленных записей, включая идентификатор записи в сообщении. Но не детерминированным способом, время от времени, когда я пытаюсь выбрать запись по ее идентификатору, запись не может быть найдена. Это потому, что транзакция еще не завершена, и запись фактически не добавляется в таблицу. Если я могу только отложить выполнение триггера после завершения транзакции, все будет работать.

Чтобы получить более качественные ответы, позвольте мне объяснить ситуацию еще ближе к реальному миру. Фактический сценарий немного сложнее, чем я объяснял ранее. Исходный код можно найти здесь, если кому-то это интересно. Не имея причин, по которым я не собираюсь входить, мне нужно отправить уведомление из другой базы данных, чтобы уведомление было отправлено, например:

PERFORM * FROM dblink('hq','SELECT pg_notify(''' || channel || ''', ''' || payload || ''')');

Я уверен, что ситуация намного сложнее.

Ответ 1

В транзакции запускаются триггеры (включая всевозможные отложенные триггеры).

Но это не проблема, потому что уведомления все равно между.

Руководство по NOTIFY:

NOTIFY взаимодействует с транзакциями SQL несколькими важными способами. Во-первых, если a NOTIFY выполняется внутри транзакции, уведомление события не доставляются до тех пор, пока транзакция не будет совершено. Это уместно, так как если транзакция прерывается, все команды внутри него не имели эффекта, включая NOTIFY. Но он может быть обескуражен, если ожидать событий уведомления быть доставлен немедленно. Во-вторых, если сеанс прослушивания получает сигнал уведомления во время транзакции, уведомление событие не будет доставлено подключенному клиенту до транзакция завершается (либо совершено, либо прервано). Опять же, рассуждение состоит в том, что если уведомление было доставлено в течение транзакция, которая была позже прервана, хотелось бы, чтобы уведомление как-то отменить - но сервер не может "вернуть" уведомление как только он отправил его клиенту. Значения уведомлений только между транзакциями.. Результатом этого является то, что приложения с использованием NOTIFY для сигнализации в реальном времени должны стараться их короткие транзакции.

Смелый акцент мой.

pg_notify() - это просто удобная функция-оболочка для команды SQL NOTIFY.

Если некоторые строки не могут быть найдены после получения уведомления, должна быть другая причина! Найди его. Вероятные кандидаты:

  • Параллельные транзакции, мешающие
  • Триггеры делают что-то большее или иное, чем вы думаете.
  • Всевозможные ошибки программирования.

В любом случае, как и в руководстве, рекомендуется сохранить транзакции, которые отправляют короткие уведомления.

dblink

Что касается вашего последующего добавления:

PERFORM * FROM dblink('hq','SELECT pg_notify(''' || channel || ''', ''' || payload || ''')');

..., который следует переписать с помощью format(), чтобы упростить и сделать синтаксис безопасным:

PRERFORM dblink('hq', format('NOTIFY %I, %L', channel, payload));

dblink - это игровой сменщик здесь, потому что он открывает отдельную транзакцию в другой базе данных. Это иногда используется для подделки автономной транзакции.

dblink() ждет завершения удаленной команды. Таким образом, удаленная транзакция, скорее всего, будет совершена в первую очередь. Руководство:

Функция возвращает строки (строки), созданные запросом.

Если вы можете отправить уведомление из той же транзакции, это будет чистое решение.

Обходной путь для dblink

Если уведомления должны отправляться из другой транзакции, есть временное решение с dblink_send_query():

dblink_send_query отправляет запрос, выполняемый асинхронно, то есть без немедленного ожидания результата.

DO  -- or plpgsql function
$$
BEGIN

-- do stuff

PERFORM dblink_connect   ('hq', 'your_connstr_or_foreign_server_here');
PERFORM dblink_send_query('con1', format('SELECT pg_sleep(3); NOTIFY %I, %L ', 'Channel', 'payload'));
PERFORM dblink_disconnect('con1');
END
$$;

Если вы сделаете это прямо до конца транзакции, ваша локальная транзакция получит 3 секунды (pg_sleep(3)), чтобы начать, Выберите необходимое количество секунд.

Существует некоторая неопределенность в отношении этого подхода, так как вы не получаете сообщения об ошибке, если что-то пойдет не так. Для безопасного решения вам нужен другой дизайн. После успешной отправки команды шансы на то, что она все еще не пройдены, очень малы. Шанс, что удаленные уведомления пропущены, кажется намного выше, но уже встроенный в ваше текущее решение.

Безопасная альтернатива

Более безопасной альтернативой было бы записать в таблицу очередей и опросить ее, как описано в @Bohemian answer. Этот связанный ответ демонстрирует, как безопасно проводить опрос:

Ответ 2

Я отправляю это как ответ, предполагая, что фактическая проблема, которую вы пытаетесь решить, откладывает выполнение внешнего процесса до завершения транзакции (а не проблемы XY ", которую вы пытаетесь решить с помощью триггера Кунг-фу).

Наличие базы данных говорит, что приложение делает что-то, это сломанный шаблон. Он сломан, потому что:

  • Нет никакого возврата, если приложение не получит сообщение, например, потому что оно вниз, сеть взрывается, что угодно. Даже приложение, отвечающее с подтверждением (которое не может), не устранит эту проблему (см. Следующую информацию)
  • Там нет разумного способа повторить работу, если приложение получает сообщение, но не может его завершить (по какой-либо причине)

В отличие от этого, используя базу данных в качестве постоянной очереди и имея приложение для опроса, для ее работы и отнимать работу из очереди при завершении работы, не имеет ни одной из вышеперечисленных проблем.

Есть много способов добиться этого. Тот, который я предпочитаю, должен иметь некоторый процесс (обычно триггер для вставки, обновления и удаления) помещать данные в таблицу "queue". Проведите еще один опрос процесса, который следует выполнить для работы, и удалите из таблицы, когда работа будет завершена.

Он также добавляет некоторые другие преимущества:

  • Производство и потребление работы развязаны, что означает, что вы можете безопасно убивать и перезапускать приложение (что должно происходить время от времени, например, развертывание) - таблица очередей будет радостно расти, пока приложение не работает, и будет стекать когда приложение будет обратно. Вы даже можете заменить приложение совершенно новым.
  • Если по какой-либо причине вы хотите инициировать обработку определенных элементов, вы можете просто вручную вставить строки в таблицу очередей. Я сам использовал эту технику, чтобы инициировать обработку всех элементов в базе данных, которые необходимо инициализировать, будучи помещенными в очередь один раз. Важно отметить, что мне не нужно было делать поверхностное обновление для каждой строки, чтобы запустить триггер.
  • Как вам кажется, небольшая задержка может быть добавлена ​​путем добавления столбца метки времени в таблицу очередей и запроса опроса выбрать только строки, которые старше, чем (скажем) 1 секунду, что дает время базы данных для завершения транзакции
  • Вы не можете перегружать приложение. Приложение будет читать только столько работы, сколько может справиться. Если ваша очередь растет, вам нужно более быстрое приложение или больше приложений. Если несколько потребителей работают, concurrency можно решить (например), добавив столбец "токенов" в таблицу очередей

Очереди, поддерживаемые таблицами базы данных, являются основой того, как постоянные очереди реализуются на платформах на основе коммерческого уровня, поэтому шаблон хорошо тестируется, используется и понимается.

Оставьте базу данных делать то, что она делает лучше всего, и единственное, что она делает: Управление данными. Не пытайтесь сделать сервер базы данных на сервере приложений.