Цикл триггеров SQL Server

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

например:

  • У меня есть две таблицы пользователей, users_V1 и users_V2, когда пользователь обновляется с одним из приложений V1, он активирует триггер, обновляющий его в users_V2.

  • Если я хочу добавить тот же триггер в таблицу V2, чтобы обновить данные в V1, когда пользователь обновляется в V2, будет он входит в бесконечную петлю? Есть ли способ избежать этого.

8 ответов


Я не рекомендую явно отключать триггер во время обработки - это может вызвать странные побочные эффекты.

самый надежный способ обнаружения (и предотвращения) циклов в триггере-использовать CONTEXT_INFO().

пример:

CREATE TRIGGER tr_Table1_Update
ON Table1
FOR UPDATE AS

DECLARE @ctx VARBINARY(128) 
SELECT @ctx = CONTEXT_INFO() 
IF @ctx = 0xFF
    RETURN

SET @ctx = 0xFF

-- Trigger logic goes here

посмотреть этой ссылке для более подробного примера.


Примечание CONTEXT_INFO() в SQL Server 2000:

контекстная информация, поддерживается, но, видимо,


  • использовать функция trigger_nestlevel() для ограничения рекурсии триггера или

  • проверьте целевую таблицу, необходимо ли обновление вообще:

    IF (SELECT COUNT(1) 
    FROM users_V1 
    INNER JOIN inserted ON users_V1.ID = inserted.ID
    WHERE users_V1.field1 <> inserted.field1
    OR users_V1.field2 <> inserted.field2) > 0 BEGIN
    
    UPDATE users_V1 SET ...
    

У меня была точно такая же проблема. Я попытался использовать CONTEXT_INFO (), но это переменная сеанса, и поэтому она работает только в первый раз! Тогда в следующий раз, когда триггер срабатывает во время сеанса, это не сработает. Поэтому я закончил с использованием переменной, которая возвращает уровень гнезда в каждом из затронутых триггеров для выхода.

пример:

CREATE TRIGGER tr_Table1_Update
ON Table1
FOR UPDATE AS
BEGIN
      --Prevents Second Nested Call
      IF @@NESTLEVEL>1 RETURN 

      --Trigger logic goes here
END

Примечание: или используйте @@NESTLEVEL>0, если вы хотите остановить все вложенные вызовы

еще одна заметка -- кажется, есть много путаницы в этой статье о вложенных вызовах и рекурсивных вызовах. Оригинальный плакат имел в виду вложенный триггер, где один триггер вызовет срабатывание другого триггера, который вызовет срабатывание первого триггера снова и так далее. Это вложенное, но, согласно SQL Server, не рекурсивное, потому что триггер не вызывает/не запускает себя напрямую. Рекурсия-это не то, где "один триггер [вызывает] другой". Это вложенное, но не обязательно рекурсивное. Вы можете проверить это включение / выключение рекурсии и вложенности с некоторыми настройками, упомянутыми здесь:сообщение в блоге о вложенности


Я с лагерем no triggers для этого конкретного сценария проектирования. Сказав это, с ограниченными знаниями о том, что делает ваше приложение и почему оно это делает, вот мой общий анализ:

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

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

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

Я предпочитаю управлять интерфейсом к базовым таблицам больше - с помощью представлений или UDFs или SPs. Пользователи никогда не получают прямого доступа к таблице. Еще один момент здесь заключается в том, что вы можете представить один вид "пользователей" или UDF, объединяющий соответствующие базовые таблицы, даже не зная об этом - возможно, дойдя до точки, где нет даже никакой "синхронизации", необходимой, поскольку новые атрибуты находятся в системе EAV, если вам нужна такая патологическая гибкость или в какой - то другой структуре, которая все еще может быть объединена-скажем, внешний применить UDF и т. д.


вам придется создать своего рода обнаружение замыкания на себя в вашем триггере. Возможно, используя оператор "if exists", чтобы увидеть, существует ли запись, прежде чем вводить ее в следующую таблицу. Похоже, что он войдет в бесконечный цикл так, как он настроен в настоящее время.


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


попробуйте что-то вроде (Я не беспокоился о триггере create, поскольку вы явно уже знаете, как написать эту часть):

update t
set field1 = i.field1
field2 = i.field2
from inserted i
join table1 t on i.id  = t.id
where field1 <> i.field1 OR field2 <> i.field2

рекурсия в триггерах, то есть один триггер, вызывающий другой, ограничивается 32 уровня

в каждом триггере просто проверьте, существует ли строка, которую вы хотите вставить.

пример

CREATE TRIGGER Table1_Synchronize_Update ON [Table1] FOR UPDATE AS
BEGIN
  UPDATE  Table2
  SET     LastName = i.LastName
          , FirstName = i.FirstName
          ,  ... -- Every relevant field that needs to stay in sync
  FROM    Table2 t2
          INNER JOIN Inserted i ON i.UserID = t2.UserID
  WHERE   i.LastName <> t2.LastName
          OR i.FirstName <> t2.FirstName
          OR ... -- Every relevant field that needs to stay in sync
END

CREATE TRIGGER Table1_Synchronize_Insert ON [Table1] FOR INSERT AS
BEGIN
  INSERT INTO Table2
  SELECT i.*
  FROM   Inserted i
         LEFT OUTER JOIN Table2 t2 ON t2.UserID = i.UserID
  WHERE  t2.UserID IS NULL
END

CREATE TRIGGER Table2_Synchronize_Update ON [Table2] FOR UPDATE AS
BEGIN
  UPDATE  Table1
  SET     LastName = i.LastName
          , FirstName = i.FirstName
          ,  ... -- Every relevant field that needs to stay in sync
  FROM    Table1 t1
          INNER JOIN Inserted i ON i.UserID = t1.UserID
  WHERE   i.LastName <> t1.LastName
          OR i.FirstName <> t1.FirstName
          OR ... -- Every relevant field that needs to stay in sync
END

CREATE TRIGGER Table2_Synchronize_Insert ON [Table2] FOR INSERT AS
BEGIN
  INSERT INTO Table1
  SELECT i.*
  FROM   Inserted i
         LEFT OUTER JOIN Table1 t1 ON t1.UserID = i.UserID
  WHERE  t1.UserID IS NULL
END