Tetikleyicide INSERTED ve DELETED tablolarına katılmakta acımasız performans


13

Belirli bir sütundan belirli bir değerden başka bir değere değişen belirli bir sütunu izleyen bir tabloda bir GÜNCELLEME tetikleyicim var. Bu durumda, tek bir UPDATE deyimi aracılığıyla başka bir tablodaki bazı ilgili verileri güncelleştirir.

Tetikleyicinin yaptığı ilk şey, güncellenmiş satırların bu sütunun değerinin söz konusu değerden değiştirilip değiştirilmediğini kontrol etmektir. INSERTED ile DELETED arasına katılır ve bu sütundaki değeri karşılaştırır. Hiçbir şey uygun değilse, UPDATE deyimi çalışmaz, bu yüzden erken kurtarır.

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

Bu durumda, CUSTNMBR temel alınan tablonun birincil anahtarıdır. Bu tabloda (5000+ satır gibi) büyük bir güncelleme yaparsam, CUSTCLAS sütununa dokunmamış olsam bile, bu ifade YAŞ alır. Profiler'de birkaç dakika boyunca bu açıklamada durmasını izleyebilirim.

Uygulama planı tuhaf. 3.714 yürütme ve ~ 18.5 milyon çıkış satırı içeren bir Eklenmiş Tarama gösterir. Bu, CUSTCLAS sütunundaki bir filtreden geçer. Bunu (iç içe döngü aracılığıyla) yalnızca bir kez yürüten ve 5000 çıkış satırı olan Silinmiş Tarama'ya (CUSTCLAS üzerinde de filtrelenir) birleştirir.

Buna neden olmak için burada ne aptalca bir şey yapıyorum? Tetikleyicinin çok satırlı güncellemeleri kesinlikle doğru şekilde işlemesi gerektiğini unutmayın.

DÜZENLE :

Ayrıca böyle yazmayı denedim (EXISTS'in hoş olmayan bir şey yapması durumunda), ama yine de korkunç.

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" den kurtulabilir misin? Ben sadece tek bir durum olup olmadığını görmek için kontrol eğer gerekli olmayabilir bazı yükü neden olduğunu düşünürdüm ...
JHFB

Yanıtlar:


11

Sen açık kullanılarak değerlendirilebilir INNER MERGE JOINveya INNER HASH JOINsen muhtemelen daha sonra tekrar muhtemelen daha iyi durumda sadece içeriğini ekleme olan tetikleyici bu tabloları kullandığınızı ipuçları ancak verilen insertedve deletedendeksli Tabloları #temptablolar ve onunla yapılıyor.

Kendileri için otomatik olarak oluşturulmuş yararlı dizinler almazlar.


Tamam, bu onu çok hızlandırıyor, ancak basamaklı tetikleyici yürütme potansiyeli var. Her tetikleyicide aynı geçici tablo adlarını (#i, #d) kullanırsam çakışırlar. Her tetikleyicide farklı bir geçici tablo adı kullanmaktan daha iyi / daha güvenli bir çözüm var mı?
db2

(Üzerinde tanımlı bir birincil anahtarla tablo değişkenleri kullanarak değerlendirmek Could CUSTNMBRbenzersiz bir kümelenmiş dizin oluşturmak için) ve kullanımı OPTION (RECOMPILE)bu satır sayısı göz önünde almak için ipucu ya da belki sadece gibi belirli bir adlandırma kuralı kullanmak#i_dbo_YourTable
Martin Smith

Sanırım onları adlandırmaya razı olacağım #trigger_name_i. Tablo değişkenleri ile gitmek, ben açık CREATE TABLEs ile kodu daha da karışıklık zorunda kalacağım. Basamaklı tetikleyicilerimiz var, ancak yinelemeli tetikleyiciler yok, bu yüzden güvende olacağımı düşünüyorum ...
db2

Bu amaç için geçici bir tablo yerine bir tablo değişkeni öneririm; tablo değişkenleri hala birincil ve ikincil (benzersiz) dizinlere sahip olabilir, tetikleyici çıkışları ve tablo değişkenleri yalnızca bu tetikleyici yürütme için kapsamlandığında otomatik olarak temizlenir (aynı addaki diğer tablo değişkenleriyle daha yüksek veya daha düşük çağrı yığını). Tablo tanımı kod yüküne kaydetmek için, her biri için bir tablo türü tanımlayın ve tablo değişkenlerini bildirmek için tür adını kullanın.
Chris Smith

@ChrisSmith sık sık ihtiyacınız olacak OPTION (RECOMPILE)böylece kardinalite dikkate alınır.
Martin Smith

10

Bu cevaplandı biliyorum ama sadece son zamanlarda aktif olarak ortaya çıktı ve ben de milyonlarca satır içeren tablolar için bu koştum. Kabul edilen cevabı iskonto etmemekle birlikte, en azından deneyimlerimin benzer testler yaparken (bir veya daha fazla sütunun gerçekten değerlerinin değişip değişmediğini görmek) Tetik performansında önemli bir faktörün sütun (lar) olup olmadığını gösterebileceğini ekleyebilirim test edilmek aslında UPDATEifadenin bir parçasıydı . Ben arasındaki karşılaştırma sütunları bulunan insertedve deletedaslında vardı tabloları değil bir parçası UPDATEdeyimi bu alanları bir parçası olduğunu, aksi takdirde yoktu performans üzerinde çok büyük sürükle koymakUPDATEifadesi (değerlerinin gerçekte değiştirilmesine bakılmaksızın). Neden bu sütunların herhangi birinin değiştirilme olasılığını mantıklı bir şekilde ortadan kaldırabiliyorsanız, yoksa, bu mümkün olmadıkları açık bir şekilde mümkün değilse, tüm bu işlemler (yani X satırlarındaki N alanlarını karşılaştırmak için bir sorgu) içinde SETbir fıkra UPDATEaçıklamada.

Kullandığım çözüm, yalnızca Tetikleyicilerin içinde çalışan UPDATE () işlevini kullanmaktı . Bu yerleşik işlev, UPDATEifadede bir sütunun belirtilip belirtilmediğini söyler ve ilgilendiğiniz sütunlar, bir parçası değilse Tetikleyiciden çıkmak için kullanılabilir UPDATE. Bu, içinde SELECTbulunduklarını varsayarak bu sütunların UPDATEgerçek değişikliklere sahip olup olmadığını belirlemek için a ile birlikte kullanılabilir . Ben gibi görünüyor birkaç denetim tetikleri üstünde kodu var:

-- 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;

Bu mantık aşağıdaki durumlarda tetikleyicinin geri kalanına ilerler:

  1. Operasyon bir INSERT
  2. İlgili alanlardan en az biri SETbir yan tümcesinde yer alıyor UPDATE ve bir satırdaki bu sütunlardan en az biri değişti

NOT (UPDATE...) OR NOT EXISTS()Garip ya geriye görünebilir, ancak kaçınmakla tasarlanmıştır SELECTüzerine insertedve deletedilgili sütunların hiçbiri parçası iseler tablolar UPDATE.

İhtiyaçlarınıza bağlı olarak, hangi sütunların ifadenin parçası olduğunu belirlemek için COLUMNS_UPDATED () işlevi başka bir seçenektir UPDATE.


1
İyi bir nokta, UPDATE(CUSTCLAS)yanlışsa her şeyi kontrol etmeli ve atlamalıdır (+1). Güncellenmemiş sütunların satır sürümlerinde güncellenmiş olanlar kadar kolay bulunmadığını doğru bulmuyorum.
Martin Smith

@MartinSmith, bunu şu ya da bu şekilde nasıl kanıtlayabiliriz? Bununla birlikte, davranışın bulduğum şekilde öngörülebilir olup olmaması önemli olmayabilir. Sadece aynı SELECT, INSERTED ve DELETED arasında bir araya gelerek, WHERE içindeki alanların UPDATE SET'inde olup olmadığına bağlı olarak alanları gerçek farklılıklar açısından kontrol eden ciddi bir performans farkı olduğunu biliyorum. Gördüğüm davranış tutarlı, bu yüzden teorim, ama gerçek sebebi bilmek iyi / ilginç olurdu. SET'teki alanların değerleri için temel tabloya geri dönmesi gerektiğinden şüphelendim.
Solomon Rutzky

Daha önce bunun yapısına baktım. Bunu yapmanın iyi bir yol bulursa hatırlayamıyorum ya da sadece bir kolaylıkla mümkün dize ve içinden kapsamlı arama bulmak kullanılan tempdbileDBCC PAGE
Martin Smith

TAMAM. Minimal boyutlu tek bir dosyaya sahip bir örnekte bu komut dosyasınıtempdb denedim , çıktıyı not defterine yapıştırdım ve "EEEEEE" araması yaptım. Çıkışı burada ekran görüntüsünde görüyorum . Her iki satırdaki her iki sütunun sürümlerinden önce ve sonra not alın. Burada çok daha kolay yollar olabilir ama benim amacım için yeterli!
Martin Smith

Aslında tempdbsayfalarda BBBBBBveya yanında olmayan başka uzun EEEEEE dizeleri de vardır DDDDDD. Biraz daha araştırma yapmak gerekebilir! Belki de bu REPLICATEçağrı yüzünden .
Martin Smith

2

Varsa kullanarak yeniden yazmaya çalışabilirim

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'e göre, sanal INSERTED / DELETED tablolarının hiçbirine sahip olmadığından geçici tabloları veya dizinleri olan tablo değişkenlerini kullanmalısınız. Özyinelemeli tetikleme olasılığınız varsa, ad çakışmalarını önlemek için tablo değişkenlerini kullanmalısınız.

Umarım birisi orijinal yazı oldukça uzun zaman önce olduğu için bunu yararlı bulur


-1

Aşağıdaki kod bu tetikleyicinin performansını artırabilir. Ayarlamanız gereken [custclass] sütununun doğru veri türünü bilmiyordum .

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

Bu ek sütunları dahil ettiğimizi Bildirimi hafıza içinde kopyaları eklenen ve silinen size tetik kodunda bunları gerekirse tablolar. Bu tablolardaki birincil anahtarlar, aynı anda birkaç satırdan fazlasını güncellediğinizde birleştirme performansını büyük ölçüde artıracaktır. İyi şanslar!

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.