Являются ли триггеры базы данных безопасными для ограничений целостности таблицы?

Я предложил использовать триггер, чтобы проверить ограничение целостности кросс-таблицы, отвечая на этот вопрос. В комментариях было высказано мнение, что это может вызвать проблемы:

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

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

Итак, мой вопрос: Являются ли триггеры базы данных безопасными для ограничений целостности кросс-таблицы? В частности, будет ли решение ниже работать?


Подвести исходный вопрос. У нас есть таблицы

  • Player - PlayerID, PlayerName
  • Ставка - BetID, BetName
  • play_in - BetID, PlayerID

Ограничение на то, чтобы комбинация BetName и PlayerID sholud была уникальной. Определение предлагаемого триггера:

CREATE TRIGGER check_bet_name BEFORE INSERT ON plays_in 
  FOR EACH ROW BEGIN
      DECLARE bet_exists INT DEFAULT 0;
      DECLARE msg VARCHAR(255);

      SELECT 1 INTO bet_exists 
        FROM Bet AS b1
        WHERE b1.BetID = NEW.BetID
          AND EXISTS (SELECT * 
            FROM plays_in AS p JOIN Bet AS b2 USING (BetID)
            WHERE p.PlayerID = NEW.PlayerID AND b2.BetName = b1.BetName
          )
        LIMIT 1;

      IF bet_exists THEN
        SET msg = "Bet name already exists...";
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg;
      END IF;
  END//

Ответ 1

Ответ триггеры не безопасны.

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

Сделка 1:

START TRANSACTION;
INSERT INTO plays_in (BetID, PlayerID) VALUES (1,1); -- query A

Сделка 2:

START TRANSACTION;
INSERT INTO plays_in (BetID, PlayerID) VALUES (1,2); -- query B; in conflict with A, but passses

Обе транзакции:

COMMIT;

Теперь plays_in будет содержать обе вставленные записи, хотя если бы A и B выполнялись в одной транзакции, триггер выдавал бы ошибку.

Все исходные примеры могут быть получены здесь

Ответ 2

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

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

Я бы не ошибся, если бы одна реализация контр-примера была авторитетной.