Цикл запуска SQL Server - PullRequest
       30

Цикл запуска SQL Server

8 голосов
/ 10 февраля 2010

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

Например:

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

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

Ответы [ 8 ]

8 голосов
/ 10 февраля 2010

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

Самый надежный способ обнаружения (и предотвращения) циклов в триггере - использовать 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:

Контекстная информация поддерживается, но, очевидно, функция CONTEXT_INFO не поддерживается. Вы должны использовать это вместо:

SELECT @ctx = context_info
FROM master.dbo.sysprocesses
WHERE spid = @@SPID
6 голосов
/ 10 февраля 2010
  • Либо используйте 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 ...
    
3 голосов
/ 11 марта 2010

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

Пример:

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, не рекурсивный, потому что триггер не вызывает / запускает сам себя.Рекурсия НЕ там, где «один триггер [вызывает] вызов другого».Это вложенное, но не обязательно рекурсивное.Вы можете проверить это, включив / отключив рекурсию и вложенность с некоторыми настройками, упомянутыми здесь: сообщение в блоге о вложенности

1 голос
/ 10 февраля 2010

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

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

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

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

Я предпочитаю больше контролировать интерфейс к базовым таблицам - с помощью представлений, UDF или SP. Пользователи никогда не получают прямой доступ к столу. Другой момент заключается в том, что вы можете представить один «пользовательский» VIEW или UDF, объединяющий соответствующие базовые таблицы, даже не подозревая об этом пользователя - возможно, дойдя до точки, где не требуется даже никакой «синхронизации», поскольку новые атрибуты находятся в Система EAV, если вам нужна такая патологическая гибкость или какая-то другая структура, к которой все еще можно присоединиться - скажем, UDF APPLY UDF и т. Д.

0 голосов
/ 10 февраля 2010

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

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
0 голосов
/ 10 февраля 2010

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

0 голосов
/ 10 февраля 2010

Рекурсия в триггерах, то есть один триггер вызывает другой, ограничена 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
0 голосов
/ 10 февраля 2010

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

...