Цикл триггеров 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