Yuvalanmış Döngüler üzerinde yavaş çalışan bir sorguyu en iyi duruma getirme (İç Birleştirme)


39

TL; DR

Bu soru görüş almaya devam ettiğinden, burada özetleyeceğim, böylece yeni gelenler tarihe katlanmak zorunda kalmayacaklar:

JOIN table t ON t.member = @value1 OR t.member = @value2 -- this is slow as hell
JOIN table t ON t.member = COALESCE(@value1, @value2)    -- this is blazing fast
-- Note that here if @value1 has a value, @value2 is NULL, and vice versa

Bunun herkesin sorunu olmayabilir ama ON cümlelerinin hassasiyetini vurgulayarak doğru yöne bakmanıza yardımcı olabilir. Her durumda, orijinal metin gelecek antropologlar için burada:

Orjinal metin

Aşağıdaki basit sorguyu düşünün (yalnızca 3 tablo dahil)

    SELECT

        l.sku_id AS ProductId,
        l.is_primary AS IsPrimary,
        v1.category_name AS Category1,
        v2.category_name AS Category2,
        v3.category_name AS Category3,
        v4.category_name AS Category4,
        v5.category_name AS Category5

    FROM category c4
    JOIN category_voc v4 ON v4.category_id = c4.category_id and v4.language_code = 'en'

    JOIN category c3 ON c3.category_id = c4.parent_category_id
    JOIN category_voc v3 ON v3.category_id = c3.category_id and v3.language_code = 'en'

    JOIN category c2 ON c2.category_id = c3.category_id
    JOIN category_voc v2 ON v2.category_id = c2.category_id and v2.language_code = 'en'

    JOIN category c1 ON c1.category_id = c2.parent_category_id
    JOIN category_voc v1 ON v1.category_id = c1.category_id and v1.language_code = 'en'

    LEFT OUTER JOIN category c5 ON c5.parent_category_id = c4.category_id
    LEFT OUTER JOIN category_voc v5 ON v5.category_id = c5.category_id and v5.language_code = @lang

    JOIN category_link l on l.sku_id IN (SELECT value FROM #Ids) AND
    (
        l.category_id = c4.category_id OR
        l.category_id = c5.category_id
    )

    WHERE c4.[level] = 4 AND c4.version_id = 5

Bu oldukça basit bir sorgu, sadece kafa karıştırıcı olan bölüm son kategori birleşimidir, bu şekildedir, çünkü kategori 5 seviyesi var olabilir veya olmayabilir. Sorgunun sonunda, ürün kimliği (SKU ID) başına kategori bilgisi arıyorum ve bu, çok büyük bir tablo category_link'in bulunduğu yerdir. Son olarak, #Ids tablosu yalnızca 10.000 Ids içeren geçici bir tablodur.

Yürütüldüğünde, aşağıdaki gerçek yürütme planını alıyorum:

Gerçek Uygulama Planı

Gördüğünüz gibi zamanın neredeyse% 90'ı İç İçe Döngüler (İç Birleştirme) içinde geçirilir. İşte bu Yuvalanmış Döngüler hakkında ek bilgi:

İç İçe Döngüler (İç Birleştirme)

Tablo adlarının okunabilirlik için sorgu tablosu adlarını düzenlediğim için tam olarak eşleşmediğini, ancak eşleşmesinin oldukça kolay olduğunu unutmayın (ads_alt_category = category). Bu sorguyu optimize etmenin bir yolu var mı? Ayrıca, üretimde, #Ids temp tablosunun bulunmadığını, aynı 10'000 ID'nin Stored Prosedürüne iletilen bir Tablo Değerli Parametresinin olmadığını unutmayın.

İlave bilgi:

  • category_id ve parent_category_id üzerindeki kategori endeksleri
  • category_id, language_code üzerinde index_voc dizini
  • sku_id, category_id üzerindeki category_link dizini

Düzenle (çözüldü)

Kabul edilen cevabın da belirttiği gibi, sorun kategorideki JOIN kategorisindeki OR cümlecikiydi. Ancak, kabul edilen cevapta önerilen kod çok yavaş, orijinal koddan bile daha yavaştır. Çok daha hızlı ve daha temiz bir çözüm, mevcut JOIN koşulunu aşağıdaki şekilde değiştirmek içindir:

JOIN category_link l on l.sku_id IN (SELECT value FROM @p1) AND l.category_id = COALESCE(c5.category_id, c4.category_id)

Bu dakika ayarlaması, en hızlı çözümdür, kabul edilen yanıttan çift birleştirmeye karşı test edilmiştir ve valverij tarafından önerildiği gibi ÇAPRAZ BAŞVURUSU'na karşı da test edilmiştir.


Sorgu planının kalanını görmemiz gerekecek.
RBarryYoung

Sadece bir açıklama: Bu bağımlı birçok katılımcının kardinalite kestirim hataları ile karşı karşıya kalması muhtemeldir. Çoğu zaman, sorgu performansı, önemsizliği küçümseme ile raydan çıkarılır.
usr

Yürütme planı indeksler için önerilerde bulunuyor mu? Ayrıca, geçici tablolarınızda birincil anahtarlar ve dizinler ayarlayabileceğinizi unutmayın ( burada daha fazla bilgi bulabilirsiniz )

@ rbarry Mevcut çözümleri denedikten sonra hiçbir şey elde edemezsem, soruyu geliştiririm

1
Bir UNION ile sorguyu çoğaltmak ve

Yanıtlar:


17

Sorun kodun bu bölümünde görünüyor:

JOIN category_link l on l.sku_id IN (SELECT value FROM #Ids) AND
(
    l.category_id = c4.category_id OR
    l.category_id = c5.category_id
)

orbirleşme koşullarında her zaman şüpheli. Bir öneri, bunu iki birleşime bölmektir:

JOIN category_link l1 on l1.sku_id in (SELECT value FROM #Ids) and l1.category_id = cr.category_id
left outer join
category_link l1 on l2.sku_id in (SELECT value FROM #Ids) and l2.category_id = cr.category_id

Daha sonra bunu işlemek için sorgunun geri kalanını değiştirmeniz gerekir. . . coalesce(l1.sku_id, l2.sku_id)örneğin selectmaddede.


Söz konusu üzerine yapılan filtreleme miktarı katılmak, ben de değişen sınamak istiyorum JOINbir etmek CROSS APPLYile INbir biçimine geçiş EXISTSiçinde APPLY'nin WHEREmaddesi.

Teşekkürler Gordon, bu ilk şeyi sabaha test edeceğim. @ Valverij, haç başvurusuna aşina değilim, çözümünüzü daha iyi, belki de uygun bir Cevapla açıklayabilir misiniz, yani en hızlı senaryo olduğu ortaya çıktığında oy kullanabilir miyim?

3
Bu cevabı kabul ediyorum çünkü soruna işaret eden ilk cevap buydu. Ancak önerilen çözüm, orijinal koddan bile daha yavaştır, yavaştır. Ancak, OR yan tümcesinin sorun olduğunu bilmek basitçe bunun yerine ON l.category_id = ISNULL(c5.category_id, c4.category_idhile yapmaktı.
Luis Ferrao

1
@LuisFerrao. . . Ek bilgi için teşekkürler. coalesce()Doktoru doğru yönde ittiğini bilmek yararlıdır .
Gordon Linoff

9

Bahsedilen başka bir kullanıcının söylediği gibi, bu birleşme sebebi şudur:

JOIN category_link l on l.sku_id IN (SELECT value FROM #Ids) AND
(
    l.category_id = c4.category_id OR
    l.category_id = c5.category_id
)

Bunları birden fazla birleştirmeye bölmenin yanı sıra, bir CROSS APPLY

CROSS APPLY (
    SELECT [some column(s)]
    FROM category_link x
    WHERE EXISTS(SELECT value FROM #Ids WHERE value = x.sku_id)
    AND (x.category_id = c4.category_id OR x.category_id = c5.category_id)        
) l

Yukarıdaki MSDN bağlantısından:

Tablo değerli işlev, sağ girdi olarak ve dış tablo ifadesi de sol girdi olarak davranır. Sağ girdi, sol girdideki her satır için değerlendirilir ve üretilen satırlar nihai çıktı için birleştirilir .

Temel olarak, APPLYilk önce sağdaki kayıtları filtreleyen ve sonra bunları sorgunuzun kalanına uygulayan bir alt sorgu gibidir.

Bu makale ne olduğunu ve ne zaman kullanılacağını açıklamak için çok iyi bir iş çıkarmıştır: http://explainextended.com/2009/07/16/inner-join-vs-cross-apply/

Bununla birlikte, CROSS APPLYher zaman birinden daha hızlı performans göstermediğini not etmek önemlidir INNER JOIN. Pek çok durumda, muhtemelen aynı olacak. Nadir durumlarda, gerçekte daha yavaş gördüm (yine, bunların tümü tablo yapınıza ve sorgunun kendisine bağlı).

Genel bir kural olarak, kendimi çok fazla koşullu ifade içeren bir masaya katılırsam bulurum. APPLY

Ayrıca eğlenceli bir not: OUTER APPLYgibi davranacakLEFT JOIN

Ayrıca, lütfen kullanmak EXISTSyerine tercihimi not alın IN. Bir INalt sorguda yaparken , değerinizi bulduktan sonra bile tüm sonuç kümesini döndüreceğini unutmayın. Bununla birlikte EXISTS, bir eşleşme bulduğu anda alt sorguyu durduracaktır.


Bu çözümü iyice test ettim. Yazarken, oldukça yavaş ama mesajınızı başlattığınız tavsiyeyi uygulamayı unuttunuz. Yerine AND x.cat = c4.cat OR x.cat = c5.catgöre x.cat = ISNULL(c5.cat, c4.cat)oldukça bilgilendirici çünkü ve IN yan tümcesi kurtulmak, ikinci en hızlı çözümü ve bir upvote layık yaptım.
Luis Ferrao

Teşekkürler. IN çizgisinin aslında orada olmaması gerekiyordu (IN kullanarak ya da OR'a yapışarak karar veremedim), kaldıracağım.
valverij
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.