ट्रिगर में INSERTED और DELETED तालिकाओं में शामिल होने वाला अत्याचारी प्रदर्शन


13

मुझे एक टेबल पर एक UPDATE ट्रिगर मिला है जो एक विशिष्ट कॉलम के लिए एक विशिष्ट मूल्य से किसी अन्य मूल्य के लिए देखता है। जब ऐसा होता है, यह एक अद्यतन तालिका के माध्यम से किसी अन्य तालिका में कुछ संबंधित डेटा को अद्यतन करता है।

ट्रिगर करता है पहली बात यह देखने के लिए है कि क्या किसी भी अद्यतन पंक्तियों में इस स्तंभ का मान प्रश्न में मान से बदला गया था। यह केवल DELETED में शामिल हो जाता है और उस कॉलम में मूल्य की तुलना करता है। यदि कुछ भी योग्य नहीं है, तो यह जल्दी बाहर निकल जाता है ताकि अद्यतन विवरण न चले।

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 अंतर्निहित तालिका की प्राथमिक कुंजी है। यदि मैं इस तालिका (5000+ पंक्तियों) पर एक बड़ा अपडेट करता हूं, तो यह कथन AGES लेता है, भले ही मैंने CUSTCLAS कॉलम को नहीं छुआ हो। मैं इसे इस विवरण पर प्रोइलर में कई मिनटों तक देख सकता हूं।

निष्पादन योजना विचित्र है। यह 3,714 निष्पादन, और ~ 18.5 मिलियन आउटपुट पंक्तियों के साथ एक सम्मिलित स्कैन दिखाता है। यह CUSTCLAS कॉलम पर एक फिल्टर के माध्यम से चलता है। यह इसे (नेस्टेड लूप के माध्यम से) हटाए गए स्कैन (CUSTCLAS पर भी फ़िल्टर किया गया) से जुड़ता है, जो केवल एक बार निष्पादित होता है और जिसमें 5000 आउटपुट पंक्तियाँ होती हैं।

इसका कारण क्या है? ध्यान दें कि ट्रिगर को बहु-पंक्ति अपडेट को ठीक से संभालना चाहिए।

संपादित करें :

मैंने इसे इस तरह से लिखने की कोशिश भी की (मामले में 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

क्या आप "टॉप 1" से छुटकारा पा सकते हैं? मुझे लगता है कि कुछ ओवरहेड का कारण बन रहा है, जिसकी आवश्यकता नहीं हो सकती है यदि आप केवल यह देखने के लिए जाँच कर रहे हैं कि क्या कोई एकल मामला है ...
JHFB

जवाबों:


11

आप स्पष्ट INNER MERGE JOINया INNER HASH JOINसंकेत का उपयोग करके मूल्यांकन कर सकते हैं, लेकिन यह देखते हुए कि आप संभवतः इन तालिकाओं का उपयोग बाद में फिर से ट्रिगर में कर रहे हैं, आप शायद बेहतर हैं बस अनुक्रमित तालिकाओं में insertedऔर deletedतालिकाओं की सामग्री को सम्मिलित करना #tempऔर इसके साथ किया जा रहा है।

उन्हें स्वचालित रूप से उनके लिए बनाए गए उपयोगी इंडेक्स नहीं मिलते हैं।


ठीक है, यह इसे बहुत तेजी से बढ़ाता है, हालांकि इसमें कैस्केडिंग ट्रिगर निष्पादन की क्षमता है। यदि मैं प्रत्येक ट्रिगर में समान अस्थायी तालिका नाम (#i, #d) का उपयोग करता हूं, तो वे संघर्ष करते हैं। क्या हर ट्रिगर में एक अलग अस्थायी तालिका नाम का उपयोग करने से बेहतर / सुरक्षित समाधान है?
db2

तालिका चर का उपयोग करके मूल्यांकन कर सकते हैं ( CUSTNMBRअद्वितीय क्लस्टर इंडेक्स बनाने के लिए एक प्राथमिक कुंजी के साथ ) और OPTION (RECOMPILE)पंक्तियों की संख्या का हिसाब लेने के लिए इसे प्राप्त करने के लिए संकेत का उपयोग करें या शायद किसी विशेष नामकरण सम्मेलन का उपयोग करें जैसे#i_dbo_YourTable
मार्टिन स्मिथ

मुझे लगता है कि मैं उनके नामकरण के लिए समझौता करूंगा #trigger_name_i। अगर मैं टेबल वेरिएबल के साथ जाता हूं, तो मुझे स्पष्ट क्रिएट टेबल के साथ कोड को और भी अधिक अव्यवस्थित करना होगा। हमें कैस्केडिंग ट्रिगर मिल गए हैं, लेकिन पुनरावर्ती ट्रिगर नहीं, इसलिए मुझे लगता है कि मैं सुरक्षित रहूंगा ...
db2

मैं इस उद्देश्य के लिए एक अस्थायी तालिका के बजाय एक तालिका चर सुझाता हूं; तालिका चर में अभी भी प्राथमिक और द्वितीयक (अद्वितीय) अनुक्रमणिका हो सकती हैं, जब ट्रिगर बाहर निकलता है और तालिका चर को केवल उस ट्रिगर निष्पादन के लिए स्कोप किया जाता है, तो वे स्वचालित रूप से साफ हो जाते हैं (यह उसी नाम के अन्य तालिका चर के साथ उच्च या निम्न पर संघर्ष नहीं करेगा। कॉल स्टैक)। तालिका परिभाषा कोड ओवरहेड पर सहेजने के लिए, प्रत्येक के लिए एक तालिका प्रकार निर्धारित करें और तालिका चर घोषित करने के लिए प्रकार नाम का उपयोग करें।
क्रिस स्मिथ

@ChrisSmith आपको अक्सर इसकी भी आवश्यकता होगी OPTION (RECOMPILE)ताकि कार्डिनैलिटी को ध्यान में रखा जाए।
मार्टिन स्मिथ

10

मुझे पता है कि यह उत्तर दिया गया है, लेकिन यह हाल ही में सक्रिय रूप में पॉप अप हुआ है और मैंने कई लाखों पंक्तियों वाली तालिकाओं के लिए इसमें भाग लिया है। स्वीकार किए गए उत्तर पर छूट न देते हुए, मैं कम से कम यह जोड़ सकता हूं कि मेरा अनुभव बताता है कि इसी तरह के परीक्षण करते समय ट्रिगर प्रदर्शन में एक महत्वपूर्ण कारक (यह देखने के लिए कि क्या एक या एक से अधिक स्तंभों में वास्तव में उनके मूल्य बदल गए हैं) स्तंभ है या नहीं (है) परीक्षण किया जाना वास्तव में UPDATEबयान का हिस्सा था । मैं के बीच कि की तुलना कॉलम पाया insertedऔर deletedतालिकाओं कि वास्तव में थे नहीं के हिस्से के UPDATEबयान है कि अन्यथा वहाँ नहीं था अगर उन क्षेत्रों का हिस्सा थे प्रदर्शन पर बहुत बड़ा खींचें डालUPDATEबयान (उनके मूल्य की परवाह किए बिना वास्तव में बदला जा रहा है)। यह निर्धारित करने के लिए कि क्या कुछ भी बदल गया है, यदि यह निर्धारित करने के लिए कि क्या कोई परिवर्तन हो सकता है, तो यह सब क्यों काम करते हैं (यानी एक एक्स पंक्तियों में एन क्षेत्रों की तुलना करने के लिए एक क्वेरी), जो स्पष्ट रूप से संभव नहीं है यदि वे मौजूद नहीं थे बयान के SETखंड में UPDATE

समाधान जो मैंने नियोजित किया था वह UPDATE () फ़ंक्शन का उपयोग करने के लिए था जो केवल ट्रिगर के अंदर काम करता है। यह बिल्ट-इन फ़ंक्शन आपको बताता है कि क्या एक कॉलम UPDATEस्टेटमेंट में निर्दिष्ट किया गया था और ट्रिगर से बाहर निकलने के लिए इस्तेमाल किया जा सकता है यदि आप जिन कॉलम के बारे में चिंतित हैं, उनका हिस्सा नहीं है UPDATE। इसका उपयोग यह 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और deletedतालिकाओं पर करने से बचने के लिए डिज़ाइन किया गया है UPDATE

आपकी आवश्यकताओं के आधार पर, COLUMNS_UPDATED () फ़ंक्शन यह निर्धारित करने के लिए एक और विकल्प है कि कौन सा कॉलम UPDATEस्टेटमेंट का हिस्सा है ।


1
अच्छी बात यह है कि उन्हें जाँच करनी चाहिए UPDATE(CUSTCLAS)और यदि झूठी (+1) है तो पूरी बात छोड़ दें। मुझे नहीं लगता कि आप सही हैं कि गैर अपडेट किए गए कॉलम पंक्तिबद्ध संस्करणों में आसानी से उपलब्ध नहीं हैं, हालांकि अपडेट किए गए हैं।
मार्टिन स्मिथ

@MartinSmith, हम इसे एक या दूसरे तरीके से साबित करने के बारे में क्या करेंगे? हालाँकि, यह बात मायने नहीं रखती कि व्यवहार मेरे द्वारा किए गए तरीके से अनुमानित है। मुझे सिर्फ इतना पता है कि यह एक ही सेलेक्ट करने वाला एक कठोर प्रदर्शन अंतर है, INSERTED और DELETED के बीच जुड़ना, वास्तविक अंतर के लिए फ़ील्ड्स की जाँच करना, इस पर निर्भर करता है कि WHERE में फ़ील्ड UPDATE के SET में थे या नहीं। मैंने जो व्यवहार देखा है वह सुसंगत है, इसलिए मेरा सिद्धांत है, लेकिन वास्तविक कारण जानने के लिए यह अच्छा / दिलचस्प होगा। मुझे संदेह था कि एसईटी में शामिल क्षेत्रों को अपने मूल्य के लिए आधार तालिका में वापस नहीं जाना था।
सोलोमन रटज़की

मैंने इससे पहले की संरचना को देखा है। मुझे याद नहीं है कि क्या मुझे यह करने का एक अच्छा तरीका मिला या मैंने बस आसानी से सक्षम स्ट्रिंग और एक विस्तृत खोज का उपयोग tempdbकियाDBCC PAGE
मार्टिन स्मिथ

ठीक। एक न्यूनतम आकार वाली एकल फ़ाइल के साथ एक उदाहरण पर tempdbमैंने इस स्क्रिप्ट को आज़माया , आउटपुट को नोटपैड में चिपकाया और "ईईईईईईईई" खोजा। मुझे यहाँ स्क्रीनशॉट में आउटपुट दिखाई देता है । दोनों पंक्तियों में दोनों स्तंभों के पहले और बाद के संस्करणों पर ध्यान दें। यहाँ मेरे उद्देश्यों के लिए बहुत आसान तरीके हो सकते हैं लेकिन पर्याप्त हैं!
मार्टिन स्मिथ

हालांकि वास्तव में में अन्य लंबी eeeeee तार देखते हैं tempdbपृष्ठों के बगल में नहीं BBBBBBया DDDDDD। कुछ और जांच करनी पड़ सकती है! हालांकि शायद यह REPLICATEकॉल के कारण है ।
मार्टिन स्मिथ

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

डेव के अनुसार, आपको इंडेक्स के साथ टेम्प टेबल या टेबल वेरिएबल का उपयोग करना चाहिए, क्योंकि वर्चुअल इंसर्टेड / DELETED टेबल में कोई नहीं है। यदि आपके पास पुनरावर्ती ट्रिगर की संभावना है, तो आपको नाम टकराव से बचने के लिए तालिका चर का उपयोग करना चाहिए।

आशा है कि किसी को यह उपयोगी लगता है क्योंकि मूल पोस्ट काफी समय पहले था ...


-1

निम्न कोड इस ट्रिगर के प्रदर्शन को बढ़ा सकता है। मुझे [कस्टक्लास] कॉलम का सही डेटा प्रकार नहीं पता था इसलिए आपको इसे समायोजित करने की आवश्यकता है।

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

ध्यान दें कि यदि आप अपने ट्रिगर कोड में उनकी आवश्यकता है तो सम्मिलित और हटाए गए तालिकाओं की मेमोरी प्रतियों में आप इन अतिरिक्त कॉलमों को शामिल कर सकते हैं । इन तालिकाओं पर प्राथमिक कुंजियाँ एक साथ कुछ पंक्तियों से अधिक अद्यतन करने पर जुड़ने के प्रदर्शन में बहुत वृद्धि करेंगी। सौभाग्य!

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.