การซิงโครไนซ์โดยใช้ทริกเกอร์


11

ฉันมีข้อกำหนดคล้ายกับการสนทนาก่อนหน้านี้ที่:

ฉันมีสองตาราง[Account].[Balance]และ[Transaction].[Amount]:

CREATE TABLE Account (
      AccountID    INT
    , Balance      MONEY
);

CREATE TABLE Transaction (
      TransactionID INT
     , AccountID    INT
    , Amount      MONEY
);

เมื่อมีการแทรก update หรือลบกับ[Transaction]ตารางที่ควรได้รับการปรับปรุงขึ้นอยู่กับ[Account].[Balance][Amount]

ขณะนี้ฉันมีทริกเกอร์ให้ทำงานนี้:

ALTER TRIGGER [dbo].[TransactionChanged] 
ON  [dbo].[Transaction]
AFTER INSERT, UPDATE, DELETE
AS 
BEGIN
IF  EXISTS (select 1 from [Deleted]) OR EXISTS (select 1 from [Inserted])
    UPDATE [dbo].[Account]
    SET
    [Account].[Balance] = [Account].[Balance] + 
        (
            Select ISNULL(Sum([Inserted].[Amount]),0)
            From [Inserted] 
            Where [Account].[AccountID] = [Inserted].[AccountID]
        )
        -
        (
            Select ISNULL(Sum([Deleted].[Amount]),0)
            From [Deleted] 
            Where [Account].[AccountID] = [Deleted].[AccountID]
        )
END

แม้ว่าสิ่งนี้ดูเหมือนจะใช้งานได้ แต่ฉันมีคำถาม:

  1. ทริกเกอร์เป็นไปตามหลักการ ACID ของฐานข้อมูลเชิงสัมพันธ์หรือไม่? มีโอกาสใด ๆ ที่เม็ดมีดอาจถูกคอมมิต แต่ทริกเกอร์ล้มเหลว?
  2. คำพูดของฉันIFและUPDATEดูแปลก ๆ มีวิธีใดที่ดีกว่าในการอัปเดต[Account]แถวที่ถูกต้อง?

คำตอบ:


13

1. ทริกเกอร์เป็นไปตามหลักการ ACID ของฐานข้อมูลเชิงสัมพันธ์หรือไม่? มีโอกาสใด ๆ ที่เม็ดมีดอาจถูกคอมมิต แต่ทริกเกอร์ล้มเหลว?

คำถามนี้มีคำตอบบางส่วนในคำถามที่เกี่ยวข้องซึ่งคุณเชื่อมโยง รหัสทริกเกอร์จะดำเนินการในบริบทการทำธุรกรรมเช่นเดียวกับคำสั่ง DML ที่ทำให้มันเกิดไฟไหม้รักษาส่วนอะตอมของหลักการกรดที่คุณพูดถึง คำสั่งวิกฤติและรหัสทริกเกอร์ทั้งประสบความสำเร็จหรือล้มเหลวเป็นหน่วย

คุณสมบัติ ACIDยังรับประกันการทำธุรกรรมทั้งหมด (รวมทั้งรหัสไก) จะออกจากฐานข้อมูลในรัฐที่ไม่ละเมิดข้อ จำกัด ใด ๆ อย่างชัดเจน ( สอดคล้อง ) และผลกระทบใด ๆ มุ่งมั่นที่จะได้รับคืนจะอยู่รอดความผิดพลาดของฐานข้อมูล ( Durable )

เว้นแต่ธุรกรรมโดยรอบ (อาจจะโดยนัยหรือโดยอัตโนมัติ) กำลังทำงานที่SERIALIZABLEระดับการแยกคุณสมบัติแยกจะไม่รับประกันโดยอัตโนมัติ กิจกรรมฐานข้อมูลอื่น ๆ ที่เกิดขึ้นพร้อมกันอาจรบกวนการทำงานที่ถูกต้องของรหัสทริกเกอร์ของคุณ ตัวอย่างเช่นยอดคงเหลือในบัญชีอาจมีการเปลี่ยนแปลงโดยเซสชั่นอื่นหลังจากที่คุณอ่านมันและก่อนที่คุณจะอัปเดต - สภาพการแข่งขันคลาสสิก

2. ข้อความ IF และ UPDATE ของฉันดูแปลก มีวิธีใดที่ดีกว่าในการอัปเดตแถว [บัญชี] ที่ถูกต้อง

มีเหตุผลที่ดีมากที่คำถามอื่น ๆ ที่คุณเชื่อมโยงไปนั้นไม่ได้นำเสนอโซลูชั่นแบบอิงทริกเกอร์ รหัสทริกเกอร์ที่ออกแบบมาเพื่อให้โครงสร้างของการทำข้อมูลให้ตรงกันนั้นมีความยากมากในการทดสอบและทดสอบอย่างถูกต้อง แม้แต่ผู้ใช้ SQL Server ขั้นสูงที่มีประสบการณ์หลายปียังคงต้องเผชิญกับปัญหานี้

การบำรุงรักษาประสิทธิภาพที่ดีในเวลาเดียวกันกับการรักษาความถูกต้องในทุกสถานการณ์และหลีกเลี่ยงปัญหาเช่นการหยุดชะงักช่วยเพิ่มมิติของความยากพิเศษ รหัสทริกเกอร์ของคุณนั้นใกล้จะแข็งแกร่งและอัปเดตยอดคงเหลือของทุกบัญชีแม้ว่าจะมีการแก้ไขเพียงธุรกรรมเดียว มีความเสี่ยงและความท้าทายทุกประเภทด้วยโซลูชั่นแบบทริกเกอร์ซึ่งทำให้งานไม่เหมาะกับใครบางคนที่ค่อนข้างใหม่สำหรับเทคโนโลยีนี้

เพื่อแสดงปัญหาบางอย่างฉันจะแสดงโค้ดตัวอย่างด้านล่าง นี่ไม่ใช่วิธีการแก้ปัญหาที่ผ่านการทดสอบอย่างเข้มงวด (ตัวเรียกใช้ยาก!) และฉันไม่แนะนำให้คุณใช้มันเป็นอย่างอื่นนอกจากแบบฝึกหัดการเรียนรู้ สำหรับระบบจริง ๆ โซลูชันที่ไม่ใช้ทริกเกอร์มีประโยชน์ที่สำคัญดังนั้นคุณควรตรวจสอบคำตอบของคำถามอื่นอย่างระมัดระวังและหลีกเลี่ยงแนวคิดทริกเกอร์โดยสมบูรณ์

ตารางตัวอย่าง

CREATE TABLE dbo.Accounts
(
    AccountID integer NOT NULL,
    Balance money NOT NULL,

    CONSTRAINT PK_Accounts_ID
    PRIMARY KEY CLUSTERED (AccountID)
);

CREATE TABLE dbo.Transactions
(
    TransactionID integer IDENTITY NOT NULL,
    AccountID integer NOT NULL,
    Amount money NOT NULL,

    CONSTRAINT PK_Transactions_ID
    PRIMARY KEY CLUSTERED (TransactionID),

    CONSTRAINT FK_Accounts
    FOREIGN KEY (AccountID)
    REFERENCES dbo.Accounts (AccountID)
);

การป้องกันไม่ให้ TRUNCATE TABLE

TRUNCATE TABLEทริกเกอร์ยังไม่ได้ยิง ตารางว่างต่อไปนี้มีอยู่อย่างหมดจดเพื่อป้องกันTransactionsตารางที่ถูกตัดทอน (ถูกอ้างอิงโดย foreign key ป้องกันการตัดทอนตาราง):

CREATE TABLE dbo.PreventTransactionsTruncation
(
    Dummy integer NULL,

    CONSTRAINT FK_Transactions
    FOREIGN KEY (Dummy)
    REFERENCES dbo.Transactions (TransactionID),

    CONSTRAINT CHK_NoRows
    CHECK (Dummy IS NULL AND Dummy IS NOT NULL)
);

นิยามทริกเกอร์

รหัสทริกเกอร์ต่อไปนี้ช่วยให้มั่นใจได้ว่ามีการปรับปรุงรายการบัญชีที่จำเป็นเท่านั้นและใช้SERIALIZABLEความหมายที่นั่น ในฐานะที่เป็นผลข้างเคียงที่ต้องการสิ่งนี้จะหลีกเลี่ยงผลลัพธ์ที่ไม่ถูกต้องซึ่งอาจส่งผลหากมีการใช้ระดับการแยกการกำหนดเวอร์ชันแถว รหัสยังหลีกเลี่ยงการเรียกใช้งานโค้ดทริกเกอร์หากไม่มีแถวที่ได้รับผลกระทบจากคำสั่ง source ตารางชั่วคราวและRECOMPILEคำใบ้จะถูกใช้เพื่อหลีกเลี่ยงปัญหาแผนการดำเนินการที่เกิดจากการประมาณการความไม่ถูกต้องของ cardinality:

CREATE TRIGGER dbo.TransactionChange ON dbo.Transactions 
AFTER INSERT, UPDATE, DELETE 
AS
BEGIN
IF @@ROWCOUNT = 0 OR
    TRIGGER_NESTLEVEL
    (
        OBJECT_ID(N'dbo.TransactionChange', N'TR'),
        'AFTER', 
        'DML'
    ) > 1 
    RETURN;

    SET NOCOUNT, XACT_ABORT ON;

    CREATE TABLE #Delta
    (
        AccountID integer PRIMARY KEY,
        Amount money NOT NULL
    );

    INSERT #Delta
        (AccountID, Amount)
    SELECT 
        InsDel.AccountID,
        Amount = SUM(InsDel.Amount)
    FROM 
    (
        SELECT AccountID, Amount
        FROM Inserted
        UNION ALL
        SELECT AccountID, $0 - Amount
        FROM Deleted
    ) AS InsDel
    GROUP BY
        InsDel.AccountID;

    UPDATE A
    SET Balance += D.Amount
    FROM #Delta AS D
    JOIN dbo.Accounts AS A WITH (SERIALIZABLE)
        ON A.AccountID = D.AccountID
    OPTION (RECOMPILE);
END;

การทดสอบ

รหัสต่อไปนี้ใช้ตารางตัวเลขเพื่อสร้างบัญชี 100,000 บัญชีที่มียอดเงินเป็นศูนย์:

INSERT dbo.Accounts
    (AccountID, Balance)
SELECT
    N.n, $0
FROM dbo.Numbers AS N
WHERE
    N.n BETWEEN 1 AND 100000;

รหัสทดสอบด้านล่างแทรก 10,000 รายการแบบสุ่ม:

INSERT dbo.Transactions
    (AccountID, Amount)
SELECT 
    CONVERT(integer, RAND(CHECKSUM(NEWID())) * 100000 + 1),
    CONVERT(money, RAND(CHECKSUM(NEWID())) * 500 - 250)
FROM dbo.Numbers AS N
WHERE 
    N.n BETWEEN 1 AND 10000;

ใช้เครื่องมือSQLQueryStressฉันรันการทดสอบนี้ 100 ครั้งใน 32 เธรดที่มีประสิทธิภาพดีไม่มีการหยุดชะงักและผลลัพธ์ที่ถูกต้อง ฉันยังไม่แนะนำสิ่งนี้เป็นอย่างอื่นนอกจากแบบฝึกหัดการเรียนรู้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.