EXISTS COUNT’tan daha üstün bir performansla varlığınızı kontrol edin! … Değil?


35

Bir satırın varlığını kontrol etmek zorunda kaldığımı sık sık okudum, her zaman bir COUNT yerine EXISTS ile yapılmalı.

Yine de, birkaç yeni senaryoda, sayım kullanırken bir performans artışı ölçtüm.
Desen şöyle gider:

LEFT JOIN (
    SELECT
        someID
        , COUNT(*)
    FROM someTable
    GROUP BY someID
) AS Alias ON (
    Alias.someID = mainTable.ID
)

SQL Server'da "içeride" olanları söyleme yöntemlerine aşina değilim, bu yüzden yaptığım ölçümlere mükemmel bir anlam veren EXISTS ile yorumlanmamış bir kusur olup olmadığını merak ediyordum (EXISTS RBAR olabilir mi ?!).

Bu olayla ilgili bir açıklamanız var mı?

DÜZENLE:

İşte çalıştırabileceğiniz tam bir komut dosyası:

SET NOCOUNT ON
SET STATISTICS IO OFF

DECLARE @tmp1 TABLE (
    ID INT UNIQUE
)


DECLARE @tmp2 TABLE (
    ID INT
    , X INT IDENTITY
    , UNIQUE (ID, X)
)

; WITH T(n) AS (
    SELECT
        ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    FROM master.dbo.spt_values AS S
) 
, tally(n) AS (
    SELECT
        T2.n * 100 + T1.n
    FROM T AS T1
    CROSS JOIN T AS T2
    WHERE T1.n <= 100
    AND T2.n <= 100
)
INSERT @tmp1
SELECT n
FROM tally AS T1
WHERE n < 10000


; WITH T(n) AS (
    SELECT
        ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    FROM master.dbo.spt_values AS S
) 
, tally(n) AS (
    SELECT
        T2.n * 100 + T1.n
    FROM T AS T1
    CROSS JOIN T AS T2
    WHERE T1.n <= 100
    AND T2.n <= 100
)
INSERT @tmp2
SELECT T1.n
FROM tally AS T1
CROSS JOIN T AS T2
WHERE T1.n < 10000
AND T1.n % 3 <> 0
AND T2.n < 1 + T1.n % 15

PRINT '
COUNT Version:
'

WAITFOR DELAY '00:00:01'

SET STATISTICS IO ON
SET STATISTICS TIME ON

SELECT
    T1.ID
    , CASE WHEN n > 0 THEN 1 ELSE 0 END AS DoesExist
FROM @tmp1 AS T1
LEFT JOIN (
    SELECT
        T2.ID
        , COUNT(*) AS n
    FROM @tmp2 AS T2
    GROUP BY T2.ID
) AS T2 ON (
    T2.ID = T1.ID
)
WHERE T1.ID BETWEEN 5000 AND 7000
OPTION (RECOMPILE) -- Required since table are filled within the same scope

SET STATISTICS TIME OFF

PRINT '

EXISTS Version:'

WAITFOR DELAY '00:00:01'

SET STATISTICS TIME ON

SELECT
    T1.ID
    , CASE WHEN EXISTS (
        SELECT 1
        FROM @tmp2 AS T2
        WHERE T2.ID = T1.ID
    ) THEN 1 ELSE 0 END AS DoesExist
FROM @tmp1 AS T1
WHERE T1.ID BETWEEN 5000 AND 7000
OPTION (RECOMPILE) -- Required since table are filled within the same scope

SET STATISTICS TIME OFF 

SQL Server 2008R2'de (Yedi 64bit) Bu sonucu alıyorum

COUNT Sürüm:

Tablo '# 455F344D'. Tarama sayısı 1, mantıksal okuma 8, fiziksel okuma 0, okuma-devam okuma 0, loblama mantık okuma 0, loblama fiziksel okuma 0, lob okuma-okuma 0 okur.
Tablo '# 492FC531'. Tarama sayısı 1, mantıksal 30, fiziksel okuma 0, okuma-devam etme 0, lobi mantık okuma 0, lobi fiziksel okuma 0, lob okuma-okuma 0.

SQL Server Yürütme Süreleri:
CPU süresi = 0 ms, geçen süre = 81 ms.

EXISTS Sürüm:

Tablo # # 492FC531. Tarama sayısı 1, mantıksal 96 okur, fiziksel 0 okur, ileri okuma 0, ok mantık 0 okur, fiziksel
ok 0 okur, lob okundu 0 okur. Tablo '# 455F344D'. Tarama sayısı 1, mantıksal 8 okur, fiziksel 0 okur, ileri okuma 0, lob mantık 0 okur, fiziksel ok 0, okundu lob 0 okur.

SQL Server Yürütme Süreleri:
CPU süresi = 0 ms, geçen süre = 76 ms.

Yanıtlar:


43

Bir satırın varlığını kontrol etmek zorunda kaldığımı sık sık okudum, her zaman bir COUNT yerine EXISTS ile yapılmalı.

Bir şeyin her zaman gerçek olması, özellikle de veritabanlarına gelince, çok nadir görülür . SQL'de aynı semantiği ifade etmenin herhangi bir yolu vardır. Yararlı bir kural varsa, mevcut en doğal sözdizimini kullanarak sorguları yazmak (ve evet, özneldir) ve yalnızca aldığınız sorgu planı veya performans kabul edilemezse, yeniden yazmayı düşünebilirsiniz.

Buna değer, benim kendi meselemdeki değeri, varoluş sorguları en doğal biçimde kullanılarak ifade ediliyor EXISTS. Bu da benim deneyim oldu EXISTS daha iyi optimize eğilimindedir daha OUTER JOINreddetmek NULLalternatif. Kullanmak COUNT(*)ve filtrelemek =0başka bir alternatiftir, bu da SQL Server sorgu iyileştiricisinde bir miktar desteğe sahip olmakla birlikte, bunu kişisel olarak daha karmaşık sorgularda güvenilmez olarak buldum. Her durumda, EXISTSbu alternatiflerin ikisinden de (benim için) çok daha doğal görünüyor.

EXISTS ile yaptığım ölçümlere mükemmel bir anlam kazandıran kayda değer bir kusur olup olmadığını merak ediyordum.

Özel örneğiniz ilginçtir, çünkü optimize edicinin CASEifadelerdeki alt sorgular ile ilgilenme biçimini (ve EXISTSözellikle de testleri) vurgular .

CASE ifadelerindeki alt sorgular

Aşağıdaki (tamamen yasal) sorguyu göz önünde bulundurun:

DECLARE @Base AS TABLE (a integer NULL);
DECLARE @When AS TABLE (b integer NULL);
DECLARE @Then AS TABLE (c integer NULL);
DECLARE @Else AS TABLE (d integer NULL);

SELECT
    CASE
        WHEN (SELECT W.b FROM @When AS W) = 1
            THEN (SELECT T.c FROM @Then AS T)
        ELSE (SELECT E.d FROM @Else AS E)
    END
FROM @Base AS B;

SemantiğininCASE bu olan WHEN/ELSEmaddeleri olan , genel olarak metin için değerlendirilmiştir. Yukarıdaki ELSEsorguda, WHENyan tümce birden fazla satır döndürdüyse, yan tümce tatmin olmuşsa, SQL Server'ın bir hata döndürmesi yanlış olur . Bu semantiklere saygı duymak için, optimizer doğrudan geçiş tahminlerini kullanan bir plan üretir:

Geçiş tahminleri

Yuvalanmış ilmeğin birleştirilmesinin iç tarafı sadece geçiş belirteci yanlış döndüğünde değerlendirilir. Genel etki, CASEifadelerin sırayla test edilmesi ve alt sorguların yalnızca önceki ifadelerin karşılanmaması durumunda değerlendirilmesidir.

EXISTS alt sorgusu ile CASE ifadeleri

Bir CASEalt sorgunun kullanıldığı yerlerde EXISTS, mantıksal varlık testi yarı birleştirme olarak uygulanır, ancak daha sonra bir cümlenin gerekmesi durumunda normalde yarı birleşme tarafından reddedilecek satırlar korunmalıdır. Bu özel tür bir yarı birleşimden geçen satırlar, yarı birleşimin bir eşleşme bulup bulamadığını gösteren bir bayrak alır. Bu bayrak prob sütunu olarak bilinir .

Uygulamanın ayrıntıları, mantıksal alt sorgunun bir sonda sütunu ile ilişkili bir birleşme ('uygula') ile değiştirilmiş olmasıdır. Çalışma, sorgu en iyi duruma RemoveSubqInPrjgetiricisi adlı bir basitleştirme kuralı tarafından gerçekleştirilir (projeksiyondaki alt sorguyu kaldır). Ayrıntıları 8606 izleme bayrağı kullanarak görebiliriz:

SELECT
    T1.ID,
    CASE
        WHEN EXISTS 
        (
            SELECT 1
            FROM #T2 AS T2
            WHERE T2.ID = T1.ID
        ) THEN 1 
    ELSE 0
    END AS DoesExist
FROM #T1 AS T1
WHERE T1.ID BETWEEN 5000 AND 7000
OPTION (QUERYTRACEON 3604, QUERYTRACEON 8606);

EXISTSTesti gösteren giriş ağacının bir kısmı aşağıda gösterilmiştir:

ScaOp_Exists 
    LogOp_Project
        LogOp_Select
            LogOp_Get TBL: #T2
            ScaOp_Comp x_cmpEq
                ScaOp_Identifier [T2].ID
                ScaOp_Identifier [T1].ID

Bu, aşağıdakiler tarafından RemoveSubqInPrjyönetilen bir yapıya dönüştürülür :

LogOp_Apply (x_jtLeftSemi probe PROBE:COL: Expr1008)

Bu, daha önce tarif edilen probla yapılan sol yarı birleştirme uygulamasıdır. Bu ilk dönüştürme, SQL Server sorgu en iyi duruma getiricilerinde şu ana kadar kullanılabilir olan tek şeydir ve bu dönüştürme devre dışı bırakıldığında derleme başarısız olur.

Bu sorgu için olası yürütme planı şekillerinden biri, bu mantıksal yapının doğrudan uygulanmasıdır:

NLJ Semi Probe ile Katıl

Son Hesaplama Ölçeği CASE, sonda sütun değerini kullanan ifadenin sonucunu değerlendirir :

Skaler ifadeyi hesapla

Optimize, yarı birleştirme için diğer fiziksel birleşme türlerini göz önüne aldığında, plan ağacının temel şekli korunur. Yalnızca birleştirme birleştirme bir prob sütununu destekler, bu nedenle mantıksal olarak mümkün olsa da bir karma yarı birleştirme dikkate alınmaz:

Prob sütunu ile birleştir

Birleştirme Expr1008, plandaki herhangi bir operatörde bunun için bir tanım görünmemesine rağmen etiketin (önce ismiyle aynı olduğu gibi) etiketli bir ifade çıktığını fark eder. Bu yine sadece sonda sütunu. Daha önce olduğu gibi, nihai Hesaplama Skaleri, bu sonda değerini değerlendirmek için kullanır CASE.

Sorun, optimizer’in, yalnızca birleştirme (veya karma) yarı birleştirme ile kayda değer hale gelen alternatifleri tam olarak keşfetmemesidir. İç içe döngüler planında, satırların T2her yinelemedeki aralıkla eşleşip eşleşmediğini kontrol etmenin bir avantajı yoktur . Bir birleştirme veya karma planla birlikte, bu yararlı bir optimizasyon olabilir.

Sorguya bir eşleşme BETWEENbelirteci eklersek T2, gerçekleşen tek şey bu denetimin her satır için birleştirme yarı birleşiminde artık olarak gerçekleştirilmesidir (yürütme planında tespit edilmesi zor, ancak işte):

SELECT
    T1.ID,
    CASE
        WHEN EXISTS 
        (
            SELECT 1
            FROM #T2 AS T2
            WHERE T2.ID = T1.ID
            AND T2.ID BETWEEN 5000 AND 7000 -- New
        ) THEN 1 
    ELSE 0
    END AS DoesExist
FROM #T1 AS T1
WHERE T1.ID BETWEEN 5000 AND 7000;

Artık tahmin

Umarız bunun BETWEENyerine T2arama ile sonuçlanacak şekilde aşağı itilir . Normalde, optimizer bunu yapmayı düşünür (sorguda fazladan bir işaret olmadan bile). Bu tanıdığı zımni yüklemleri ( BETWEENüzerinde T1ve arasında yüklemi katılmak T1ve T2birlikte ima BETWEENüzerinde T2) onlara orijinal sorgu metninde mevcut olmadan. Ne yazık ki, başvuru-probu modeli bunun keşfedilmediği anlamına gelir.

Her iki girişte arama üretmek için sorguyu birleştirme yarı birleşimine yazmak için yollar vardır. Bir yol, sorguyu oldukça doğal olmayan bir şekilde yazmayı içerir (genellikle tercih ettiğim nedeni yitirir EXISTS):

WITH T2 AS
(
    SELECT TOP (9223372036854775807) * 
    FROM #T2 AS T2 
    WHERE ID BETWEEN 5000 AND 7000
)
SELECT 
    T1.ID, 
    DoesExist = 
        CASE 
            WHEN EXISTS 
            (
                SELECT * FROM T2 
                WHERE T2.ID = T1.ID
            ) THEN 1 ELSE 0 END
FROM #T1 AS T1
WHERE T1.ID BETWEEN 5000 AND 7000;

TOP numara planı

Bu sorguyu bir üretim ortamında yazmaktan mutlu olmazdım, sadece istenen plan şeklinin mümkün olduğunu göstermek için. Yazmanız gereken gerçek sorgu CASE, bu şekilde kullanıyorsa ve performans, bir birleştirme yarı birleşiminin prob tarafında bir arama yapılmadığı için sıkıntı çekiyorsa, sorguyu doğru sonuçları üreten farklı bir sözdizimi kullanarak yazmayı düşünebilirsiniz. daha verimli yürütme planı.


6

Argüman "vs COUNT (*) MEVCUT" rekor var olup olmadığını kontrol ile yapmaktır. Örneğin:

WHERE (SELECT COUNT(*) FROM Table WHERE ID=@ID)>0

vs

WHERE EXISTS(SELECT ID FROM Table WHERE ID=@ID)

SQL komut dosyanız COUNT(*)bir kayıt var kontrolü olarak kullanılmıyor ve bu nedenle senaryounuzda uygulanabilir olduğunu söyleyemem.


Yayınladığım senaryoya göre herhangi bir düşünce / sonuç?
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.