นี่เป็นอีกตัวเลือกหนึ่ง: ทริกเกอร์ที่อนุญาตให้อัปเดตหลายแถวและบังคับใช้รอบ มันทำงานได้โดยการข้ามผ่านสายโซ่บรรพบุรุษจนกว่าจะพบองค์ประกอบราก (กับผู้ปกครองเป็นโมฆะ) ดังนั้นการพิสูจน์ว่าไม่มีรอบ มันถูก จำกัด ไว้ที่ 10 ชั่วอายุคนเนื่องจากแน่นอนว่าวัฏจักรไม่มีที่สิ้นสุด
ใช้งานได้กับชุดแถวที่แก้ไขปัจจุบันเท่านั้นตราบใดที่การอัปเดตไม่ได้สัมผัสไอเท็มที่มีความลึกมาก ๆ ในตารางประสิทธิภาพก็ไม่ควรเลวร้ายเกินไป มันไม่ต้องไปตลอดทางขึ้นห่วงโซ่สำหรับแต่ละองค์ประกอบดังนั้นก็จะมีบางอย่างที่ส่งผลกระทบต่อประสิทธิภาพการทำงาน
ทริกเกอร์ "อัจฉริยะ" อย่างแท้จริงจะมองหารอบโดยตรงโดยการตรวจสอบเพื่อดูว่ารายการถึงตัวเองแล้วประกันตัว อย่างไรก็ตามสิ่งนี้ต้องการการตรวจสอบสถานะของโหนดที่พบก่อนหน้านี้ทั้งหมดในแต่ละลูปดังนั้นจึงต้องใช้ลูปขณะที่และการเข้ารหัสมากกว่าที่ฉันต้องการตอนนี้ นี่ไม่น่าจะแพงไปกว่านี้อีกแล้วเพราะการทำงานปกติจะไม่มีวัฏจักรและในกรณีนี้มันจะทำงานได้เร็วขึ้นกับรุ่นก่อนเท่านั้นมากกว่าโหนดก่อนหน้าทั้งหมดในแต่ละรอบ
ฉันชอบที่จะป้อนข้อมูลจาก @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;
ปรับปรุง
ฉันหาวิธีหลีกเลี่ยงการเข้าร่วมพิเศษกลับไปที่ตารางแทรก หากใครเห็นวิธีที่ดีกว่าในการทำ GROUP BY เพื่อตรวจหาผู้ที่ไม่มี NULL โปรดแจ้งให้เราทราบ
ฉันยังเพิ่มสวิตช์เป็น READ COMMITTED ถ้าเซสชันปัจจุบันอยู่ในระดับ SNAPSHOT ISOLATION วิธีนี้จะป้องกันความไม่สอดคล้องกัน แต่น่าเสียดายที่จะทำให้การบล็อกเพิ่มขึ้น นั่นเป็นสิ่งที่หลีกเลี่ยงไม่ได้สำหรับงานในมือ