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 เธรดที่มีประสิทธิภาพดีไม่มีการหยุดชะงักและผลลัพธ์ที่ถูกต้อง ฉันยังไม่แนะนำสิ่งนี้เป็นอย่างอื่นนอกจากแบบฝึกหัดการเรียนรู้