ประสิทธิภาพการทำงานที่โหดร้ายเข้าร่วมตาราง INSERTED และ DELETED ในทริกเกอร์


12

ฉันได้รับทริกเกอร์ UPDATE ในตารางที่เฝ้าดูคอลัมน์ที่เฉพาะเจาะจงเปลี่ยนจากค่าหนึ่งไปเป็นค่าอื่น ๆ เมื่อสิ่งนี้เกิดขึ้นมันจะอัพเดทข้อมูลที่เกี่ยวข้องในตารางอื่นผ่านคำสั่ง UPDATE เดียว

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

IF NOT EXISTS (
    SELECT TOP 1 i.CUSTNMBR
    FROM INSERTED i
        INNER JOIN DELETED d
            ON i.CUSTNMBR = d.CUSTNMBR
    WHERE d.CUSTCLAS = 'Misc'
        AND i.CUSTCLAS != 'Misc'
)
    RETURN

ในกรณีนี้ CUSTNMBR เป็นคีย์หลักของตารางต้นแบบ หากฉันอัปเดตจำนวนมากในตารางนี้ (พูด, 5,000+ แถว) ข้อความนี้ใช้เวลา AGES แม้ว่าฉันจะไม่ได้แตะคอลัมน์ CUSTCLAS ฉันสามารถดูมันค้างบนคำสั่งนี้เป็นเวลาหลายนาทีใน Profiler

แผนการดำเนินการเป็นที่แปลกประหลาด มันแสดงให้เห็นถึงการสแกนแทรกด้วยการประมวลผล 3,714 และแถวเอาท์พุท ~ 18.5 ล้าน ที่ไหลผ่านตัวกรองในคอลัมน์ CUSTCLAS มันจะรวมสิ่งนี้ (ผ่านลูปซ้อนกัน) ไปยังการสแกนที่ถูกลบ (ยังกรองใน CUSTCLAS) ซึ่งดำเนินการเพียงครั้งเดียวและมี 5,000 แถวเอาต์พุต

ฉันกำลังทำสิ่งที่งี่เง่าที่นี่เพื่อทำให้เกิดสิ่งนี้? โปรดทราบว่าทริกเกอร์ต้องจัดการอัพเดตหลายแถวอย่างถูกต้อง

แก้ไข :

ฉันยังพยายามเขียนแบบนี้ (ในกรณีที่ EXISTS ทำสิ่งที่ไม่พึงประสงค์) แต่มันก็ยังแย่มาก

DECLARE @CUSTNMBR varchar(31)
SELECT TOP 1 @CUSTNMBR = i.CUSTNMBR
FROM INSERTED i
    INNER JOIN DELETED d
        ON i.CUSTNMBR = d.CUSTNMBR
WHERE d.CUSTCLAS = 'Misc'
    AND i.CUSTCLAS != 'Misc'

IF @CUSTNMBR IS NULL
    RETURN

คุณช่วยกำจัด "TOP 1" ออกได้ไหม? ฉันคิดว่าจะก่อให้เกิดค่าใช้จ่ายบางอย่างที่อาจไม่จำเป็นต้องถ้าคุณเป็นเพียงการตรวจสอบเพื่อดูว่ามีกรณีเดียว ...
JHFB

คำตอบ:


10

คุณสามารถประเมินโดยใช้คำแนะนำINNER MERGE JOINหรือINNER HASH JOINคำแนะนำที่ชัดเจนแต่เนื่องจากคุณสามารถใช้ตารางเหล่านี้ได้อีกในภายหลังในทริกเกอร์คุณอาจจะดีกว่าเพียงแค่แทรกเนื้อหาของinsertedและdeletedตารางลงใน#tempตารางที่จัดทำดัชนีและดำเนินการกับมัน

พวกเขาไม่ได้รับดัชนีที่มีประโยชน์ที่สร้างขึ้นสำหรับพวกเขาโดยอัตโนมัติ


โอเคความเร็วนี้เพิ่มขึ้นอย่างมากอย่างไรก็ตามมีความเป็นไปได้สำหรับการประมวลผลทริกเกอร์แบบเรียงซ้อน ถ้าฉันใช้ชื่อตารางชั่วคราว (#i, #d) ในแต่ละทริกเกอร์พวกเขาขัดแย้งกัน มีวิธีแก้ปัญหาที่ดีกว่า / ปลอดภัยกว่าการใช้ชื่อตารางชั่วคราวในทุกทริกเกอร์หรือไม่?
db2

สามารถประเมินโดยใช้ตัวแปรตาราง (โดยมีคีย์หลักที่กำหนดไว้CUSTNMBRเพื่อสร้างดัชนีคลัสเตอร์ที่ไม่ซ้ำกัน) และใช้OPTION (RECOMPILE)คำแนะนำเพื่อให้มันคำนึงถึงจำนวนแถวหรืออาจใช้รูปแบบการตั้งชื่อเฉพาะเช่น#i_dbo_YourTable
Martin Smith

#trigger_name_iฉันคิดว่าฉันจะชำระสำหรับการตั้งชื่อพวกเขาเช่น ถ้าฉันไปกับตัวแปรตารางฉันจะต้องทำให้โค้ดยุ่งเหยิงยิ่งขึ้นด้วย CREATE TABLE เรามีทริกเกอร์แบบเรียงซ้อน แต่ไม่ใช่ทริกเกอร์แบบเรียกซ้ำดังนั้นฉันคิดว่าฉันจะปลอดภัย ...
db2

ฉันแนะนำตัวแปรตารางแทนตารางชั่วคราวสำหรับวัตถุประสงค์นี้ ตัวแปรตารางยังสามารถมีดัชนีหลักและรอง (ไม่ซ้ำกัน) ซึ่งจะถูกล้างโดยอัตโนมัติเมื่อทริกเกอร์ออกและตัวแปรตารางถูกกำหนดขอบเขตเพื่อเรียกใช้งานทริกเกอร์นั้น (จะไม่ขัดแย้งกับตัวแปรตารางอื่นที่มีชื่อเดียวกันสูงกว่าหรือต่ำกว่า call stack) หากต้องการบันทึกค่าใช้จ่ายของรหัสคำนิยามตารางให้กำหนดประเภทตารางสำหรับแต่ละรายการและใช้ชื่อประเภทเพื่อประกาศตัวแปรตาราง
Chris Smith

@ChrisSmith บ่อยครั้งที่คุณจะต้องOPTION (RECOMPILE)มีการคำนึงถึงความสำคัญของ cardinality
Martin Smith

10

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

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

-- exit on updates that do not update the only 3 columns we ETL
IF (
     EXISTS(SELECT 1 FROM DELETED) -- this is an UPDATE (Trigger is AFTER INSERT, UPDATE)
     AND (
            NOT (UPDATE(Column3) OR UPDATE(Column7)
                 OR UPDATE(Column11)) -- the columns we care about are not being updated
            OR NOT EXISTS(
                        SELECT 1
                        FROM INSERTED ins
                        INNER JOIN DELETED del
                                ON del.KeyField1 = ins.KeyField1
                                AND del.KeyField2 = ins.KeyField2
                        WHERE ins.Column3 <> del.Column3
                                 COLLATE Latin1_General_100_CS_AS -- case-sensitive compare
                        OR    ISNULL(ins.Column7, -99) <> 
                                 ISNULL(del.Column7, -99) -- NULLable INT field
                        OR    ins.[Column11] <> del.[Column11] -- NOT NULL INT field
                      )
          )
    )
BEGIN
    RETURN;
END;

ตรรกะนี้จะดำเนินต่อไปยังส่วนที่เหลือของทริกเกอร์หาก:

  1. การดำเนินการคือ INSERT
  2. อย่างน้อยหนึ่งฟิลด์ที่เกี่ยวข้องอยู่ในส่วนSETคำสั่งของ UPDATE และอย่างน้อยหนึ่งคอลัมน์ในแถวเดียวมีการเปลี่ยนแปลง

NOT (UPDATE...) OR NOT EXISTS()อาจจะดูแปลก ๆ หรือถอยหลัง แต่มันถูกออกแบบมาเพื่อหลีกเลี่ยงการทำSELECTบนinsertedและตารางถ้าไม่มีคอลัมน์ที่เกี่ยวข้องเป็นส่วนหนึ่งของ deletedUPDATE

ฟังก์ชัน COLUMNS_UPDATED ()ขึ้นอยู่กับความต้องการของคุณเป็นตัวเลือกอื่นเพื่อพิจารณาว่าคอลัมน์ใดเป็นส่วนหนึ่งของUPDATEคำสั่ง


1
จุดที่ดีที่พวกเขาควรตรวจสอบUPDATE(CUSTCLAS)และเพียงข้ามสิ่งทั้งหมดถ้าเป็นเท็จ (+1) ฉันไม่คิดว่าคุณถูกต้องว่าคอลัมน์ที่ไม่ได้อัปเดตนั้นไม่พร้อมใช้งานในเวอร์ชันแถวเหมือนที่อัปเดตแล้ว
Martin Smith

@ มาร์ตินเราจะไปพิสูจน์ทางใดทางหนึ่งได้อย่างไร แม้ว่ามันจะไม่สำคัญว่าพฤติกรรมนั้นสามารถคาดเดาได้ในลักษณะที่ฉันได้พบ ฉันเพิ่งรู้ว่ามันเป็นความแตกต่างของประสิทธิภาพการทำงานที่ยอดเยี่ยมในการเลือก SELECT เดียวกันเชื่อมต่อระหว่าง INSERTED และ DELETED ตรวจสอบความแตกต่างที่เกิดขึ้นจริงขึ้นอยู่กับว่าฟิลด์ใน WHERE อยู่ใน SET ของ UPDATE หรือไม่ พฤติกรรมที่ฉันเห็นมีความสอดคล้องดังนั้นทฤษฎีของฉัน แต่มันจะดี / น่าสนใจที่จะรู้เหตุผลที่แท้จริง ฉันสงสัยว่าฟิลด์ที่ไม่ได้อยู่ในตลาดหลักทรัพย์ต้องกลับไปที่ตารางฐานเพื่อดูค่าของพวกเขา
โซโลมอน Rutzky

ฉันเคยดูโครงสร้างของสิ่งนี้มาก่อน ฉันจำไม่ได้ถ้าฉันพบวิธีที่ดีในการทำมันหรือฉันใช้สตริงที่สามารถค้นหาได้ง่ายและค้นหาอย่างละเอียดtempdbด้วยDBCC PAGE
Martin Smith

ตกลง. ในอินสแตนซ์ที่มีไฟล์เดี่ยวขนาดเล็กที่สุดtempdbฉันลองใช้สคริปต์นี้วางผลลัพธ์ลงในแผ่นจดบันทึกแล้วค้นหา "EEEEEE" ผมเห็นการส่งออกในภาพที่นี่ หมายเหตุก่อนและหลังเวอร์ชันของทั้งสองคอลัมน์ในทั้งสองแถว อาจจะมีวิธีที่ง่ายกว่านี้ แต่เพียงพอสำหรับวัตถุประสงค์ของฉันที่นี่!
Martin Smith

แม้ว่าที่จริงมีสตริง EEEEEE ยาวอื่นในtempdbหน้าไม่ติดกับหรือBBBBBB DDDDDDอาจจะต้องทำการสอบสวนเพิ่มเติม! แม้ว่านี่อาจเป็นเพราะการREPLICATEโทร
Martin Smith

2

ฉันอาจพยายามเขียนโดยใช้ถ้ามี

IF EXISTS (SELECT TOP 1 i.CUSTNMBR     
            FROM INSERTED i         
            INNER JOIN DELETED d             
            ON i.CUSTNMBR = d.CUSTNMBR and d.custclass = 'Misc'  
            WHERE d.CUSTCLAS <>i.CUSTCLAS)    
BEGIN

--do your triggerstuff here
END

1

http://dave.brittens.org/blog/writing-well-behaved-triggers.html

ตาม Dave คุณควรใช้ temp tables หรือตัวแปร table กับ index เพราะ virtual INSERTED / DELETED table นั้นไม่มีเลย หากคุณมีความเป็นไปได้ของทริกเกอร์แบบเรียกซ้ำคุณควรใช้ตัวแปรตารางเพื่อหลีกเลี่ยงการชนกันของชื่อ

หวังว่าบางคนจะพบว่ามีประโยชน์เพราะโพสต์ต้นฉบับค่อนข้างบางเวลาที่ผ่านมา ...


-1

รหัสต่อไปนี้อาจเพิ่มประสิทธิภาพของทริกเกอร์นี้ ฉันไม่ทราบประเภทข้อมูลที่ถูกต้องของคอลัมน์[custclass]ดังนั้นคุณต้องปรับเปลี่ยน

DECLARE @i AS TABLE (CUSTNMBR VARCHAR(31) NOT NULL PRIMARY KEY, custclass VARCHAR(10) NOT NULL)
DECLARE @d AS TABLE (CUSTNMBR VARCHAR(31) NOT NULL PRIMARY KEY, custclass VARCHAR(10) NOT NULL)
INSERT INTO @i SELECT CUSTNMBR, custclass FROM inserted
INSERT INTO @d SELECT CUSTNMBR, custclass FROM deleted
IF NOT EXISTS
  (SELECT * FROM @i AS i INNER JOIN @d AS d ON d.CUSTNMBR = i.CUSTNMBR
   WHERE i.custclass <> d.custclass) RETURN

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

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