다른 옵션은 다음과 같습니다. 여러 행 업데이트를 허용하고주기를 적용하지 않는 트리거입니다. 루트 요소 (부모 NULL 포함)를 찾을 때까지 조상 체인을 통과하여 작동하므로 사이클이 없음을 증명합니다. 물론 사이클은 끝이 없기 때문에 10 세대로 제한됩니다.
현재 수정 된 행 세트에서만 작동하므로 업데이트가 테이블의 매우 많은 수의 매우 깊은 항목에 닿지 않는 한 성능이 나쁘지 않아야합니다. 각 요소의 체인까지 올라 가야하므로 성능에 약간의 영향을 미칩니다.
진정으로 "지능적인"트리거는 항목이 자체에 도달했는지 확인한 다음 bailing하여 직접 사이클을 찾습니다. 그러나 이것은 각 루프 중에 이전에 찾은 모든 노드의 상태를 확인해야하므로 WHILE 루프와 현재 원하는 것보다 많은 코딩이 필요합니다. 정상적인 작업에는 사이클이 없기 때문에 더 비싸지 않아야 하며이 경우 각 루프 동안 모든 이전 노드가 아닌 이전 세대로만 작업하는 것이 더 빠릅니다.
@AlexKuznetsov 또는 다른 사람이 스냅 샷 격리에서 어떻게 작용하는지에 대한 의견을 듣고 싶습니다. 나는 그것이 잘되지 않을 것이라고 생각하지만 더 잘 이해하고 싶습니다.
CREATE TRIGGER TR_Foo_PreventCycles_IU ON Foo FOR INSERT, UPDATE
AS
SET NOCOUNT ON;
SET XACT_ABORT ON;
IF EXISTS (
SELECT *
FROM sys.dm_exec_session
WHERE session_id = @@SPID
AND transaction_isolation_level = 5
)
BEGIN;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
END;
DECLARE
@CycledFooId bigint,
@Message varchar(8000);
WITH Cycles AS (
SELECT
FooId SourceFooId,
ParentFooId AncestorFooId,
1 Generation
FROM Inserted
UNION ALL
SELECT
C.SourceFooId,
F.ParentFooId,
C.Generation + 1
FROM
Cycles C
INNER JOIN dbo.Foo F
ON C.AncestorFooId = F.FooId
WHERE
C.Generation <= 10
)
SELECT TOP 1 @CycledFooId = SourceFooId
FROM Cycles C
GROUP BY SourceFooId
HAVING Count(*) = Count(AncestorFooId); -- Doesn't have a NULL AncestorFooId in any row
IF @@RowCount > 0 BEGIN
SET @Message = CASE WHEN EXISTS (SELECT * FROM Deleted) THEN 'UPDATE' ELSE 'INSERT' END + ' statement violated TRIGGER ''TR_Foo_PreventCycles_IU'' on table "dbo.Foo". A Foo cannot be its own ancestor. Example value is FooId ' + QuoteName(@CycledFooId, '"') + ' with ParentFooId ' + Quotename((SELECT ParentFooId FROM Inserted WHERE FooID = @CycledFooId), '"');
RAISERROR(@Message, 16, 1);
ROLLBACK TRAN;
END;
최신 정보
Inserted 테이블에 추가 조인을 피하는 방법을 알아 냈습니다. 누구든지 NULL을 포함하지 않는 것을 감지하기 위해 GROUP BY를 수행하는 더 좋은 방법을 알고 있다면 알려주십시오.
또한 현재 세션이 SNAPSHOT ISOLATION 레벨 인 경우 READ COMMITTED에 스위치를 추가했습니다. 불행하게도 차단이 증가하지만 불일치를 방지합니다. 그것은 당면한 일에 피할 수없는 일입니다.