Hangi SQL sorgusu daha hızlıdır? Birleştirme kriterlerine göre veya Nerede maddesine göre filtreleme?


99

Bu 2 sorguyu karşılaştırın. Filtreyi birleştirme kriterine mi yoksa WHEREmaddeye mi koymak daha hızlı ? Her zaman birleştirme kriterlerinde daha hızlı olduğunu hissetmişimdir çünkü mümkün olan en kısa zamanda sonuç kümesini düşürür, ancak kesin olarak bilmiyorum.

Görmek için bazı testler yapacağım, ancak hangisinin daha net okunacağı konusunda da fikir edinmek istedim.

Sorgu 1

SELECT      *
FROM        TableA a
INNER JOIN  TableXRef x
        ON  a.ID = x.TableAID
INNER JOIN  TableB b
        ON  x.TableBID = b.ID
WHERE       a.ID = 1            /* <-- Filter here? */

Sorgu 2

SELECT      *
FROM        TableA a
INNER JOIN  TableXRef x
        ON  a.ID = x.TableAID
        AND a.ID = 1            /* <-- Or filter here? */
INNER JOIN  TableB b
        ON  x.TableBID = b.ID

DÜZENLE

Bazı testler yaptım ve sonuçlar aslında çok yakın olduğunu gösteriyor, ancak WHEREcümle aslında biraz daha hızlı! =)

Filtrenin WHEREmaddeye uygulanmasının daha mantıklı olduğuna kesinlikle katılıyorum , sadece performansın etkilerini merak ediyordum.

KRİTERLERİN ELAPSED SÜRESİ: 143016 ms
ELAPSED TIME JOIN CRITERIA : 143256 ms

ÖLÇEK

SET NOCOUNT ON;

DECLARE @num    INT,
        @iter   INT

SELECT  @num    = 1000, -- Number of records in TableA and TableB, the cross table is populated with a CROSS JOIN from A to B
        @iter   = 1000  -- Number of select iterations to perform

DECLARE @a TABLE (
        id INT
)

DECLARE @b TABLE (
        id INT
)

DECLARE @x TABLE (
        aid INT,
        bid INT
)

DECLARE @num_curr INT
SELECT  @num_curr = 1
        
WHILE (@num_curr <= @num)
BEGIN
    INSERT @a (id) SELECT @num_curr
    INSERT @b (id) SELECT @num_curr
    
    SELECT @num_curr = @num_curr + 1
END

INSERT      @x (aid, bid)
SELECT      a.id,
            b.id
FROM        @a a
CROSS JOIN  @b b

/*
    TEST
*/
DECLARE @begin_where    DATETIME,
        @end_where      DATETIME,
        @count_where    INT,
        @begin_join     DATETIME,
        @end_join       DATETIME,
        @count_join     INT,
        @curr           INT,
        @aid            INT

DECLARE @temp TABLE (
        curr    INT,
        aid     INT,
        bid     INT
)

DELETE FROM @temp

SELECT  @curr   = 0,
        @aid    = 50

SELECT  @begin_where = CURRENT_TIMESTAMP
WHILE (@curr < @iter)
BEGIN
    INSERT      @temp (curr, aid, bid)
    SELECT      @curr,
                aid,
                bid
    FROM        @a a
    INNER JOIN  @x x
            ON  a.id = x.aid
    INNER JOIN  @b b
            ON  x.bid = b.id
    WHERE       a.id = @aid
        
    SELECT @curr = @curr + 1
END
SELECT  @end_where = CURRENT_TIMESTAMP

SELECT  @count_where = COUNT(1) FROM @temp
DELETE FROM @temp

SELECT  @curr = 0
SELECT  @begin_join = CURRENT_TIMESTAMP
WHILE (@curr < @iter)
BEGIN
    INSERT      @temp (curr, aid, bid)
    SELECT      @curr,
                aid,
                bid
    FROM        @a a
    INNER JOIN  @x x
            ON  a.id = x.aid
            AND a.id = @aid
    INNER JOIN  @b b
            ON  x.bid = b.id
    
    SELECT @curr = @curr + 1
END
SELECT  @end_join = CURRENT_TIMESTAMP

SELECT  @count_join = COUNT(1) FROM @temp
DELETE FROM @temp

SELECT  @count_where AS count_where,
        @count_join AS count_join,
        DATEDIFF(millisecond, @begin_where, @end_where) AS elapsed_where,
        DATEDIFF(millisecond, @begin_join, @end_join) AS elapsed_join

10
Verilere bağlı olarak, WHERE vs JOIN kriterleri farklı sonuç kümeleri döndürebilir.
OMG Ponies

5
@OMG Ponies çok doğru, ama çoğu zaman da değil.
Jon Erickson

2
Fark olarak% 5'in altında fark demezdim - onlar aynı. %% 2'lik bir fark için anlamlı olmasını istiyorsanız, sadece rastgele olmadığından emin olmak için testleri 1000 kez daha iyi çalıştırın.
TomTom

Bunun faydası, katılmadan önce verileri filtrelemektir, bu nedenle eğer x.ID olsaydı, bir a.ID'den daha fazla gelişme görmeniz daha olası olurdu
MikeT

Yanıtlar:


66

Performans açısından aynıdırlar (ve aynı planları üretirler)

Mantıksal olarak, INNER JOINbir LEFT JOIN.

Sizin durumunuzda bu şöyle görünecek:

SELECT  *
FROM    TableA a
LEFT JOIN
        TableXRef x
ON      x.TableAID = a.ID
        AND a.ID = 1
LEFT JOIN
        TableB b
ON      x.TableBID = b.ID

veya bu:

SELECT  *
FROM    TableA a
LEFT JOIN
        TableXRef x
ON      x.TableAID = a.ID
LEFT JOIN
        TableB b
ON      b.id = x.TableBID
WHERE   a.id = 1

Eski sorgu için herhangi bir fiili eşleşmeleri döndürmez a.iddışındaki 1(ile ikinci sözdizimi böylece WHERE) mantıksal olarak daha tutarlıdır.


Setleri çizdiğimde ikinci durumun neden daha tutarlı olduğunu anladım. Önceki sorguda, sınırlama a.id = 1yalnızca kesişim için geçerlidir, kesişimi hariç tutan sol kısım için geçerli değildir.
FtheBuilder

1
İlk örnekte a.id != 1, diğerinin yalnızca nerede olduğu satırlara sahip olacağı satırlar olabilir a.id = 1.
FtheBuilder

1
Diliniz net değil. "Mantıksal olarak, hala mantıklı olan işlemi yapmalısınız eğer ..." ve "mantıksal olarak daha tutarlı" mantıklı gelmiyor. Lütfen yeniden ifade edebilir misiniz?
philipxy

24

İç birleşimler için kriterlerinizi nereye koyduğunuz önemli değildir. SQL derleyicisi, her ikisini de filtrelemenin birleştirmenin altında gerçekleştiği bir yürütme planına dönüştürecektir (yani, filtre ifadeleri birleşim koşulundaymış gibi).

Filtrenin yeri sorgunun anlamını değiştirdiğinden, dış birleşimler farklı bir konudur.


Yani iç birleşimlerde önce filtreyi hesaplar ve sonra filtrenin çıktısını diğer tabloyla birleştirir veya önce iki tabloyu birleştirip sonra filtreyi uygular?
Ashwin

@Remus Rusanu - Dış-birleştirme durumunda anlambilimin nasıl değiştiğini açıklar mısınız? Filtrenin konumuna bağlı olarak farklı sonuçlar alıyorum, ancak nedenini anlayamıyorum
Ananth

3
@Ananth bir dış birleşimle, JOIN koşulunun eşleşmediği birleştirilmiş tablonun tüm sütunları için NULL elde edersiniz. Filtreler NULL'u karşılamaz ve satırları ortadan kaldırarak OUTER birleşimini bir INNER birleşimine dönüştürür.
Remus Rusanu

@Ananth, yorumunuza göre gerekli optimizasyonlarımı yaptım. Değişikliğim WHERE x.TableAID = a.ID veya x.TableAID'den ON x.TableAID = a.ID oldu. Bir OUTER birleşiminde filtrenin konumunu değiştirmek, derleyicinin Join yerine Filter yerine Filter sonra Join yapmasını sağlar. Ayrıca, Null ile eşleşmesi gerekmediği için bu sütundaki dizini de kullanabiliyordu. Sorgu yanıtı 61 saniyeden 2 saniyeye değiştirildi.
Ben Gripka

10

İki yöntem gittiği sürece.

  • JOIN / ON, tabloları birleştirmek içindir
  • NEREDE sonuçları filtrelemek için

Onları farklı kullanabilseniz de, bana her zaman bir koku gibi geliyor.

Sorun olduğunda performansla ilgilenin. Sonra bu tür "optimizasyonlara" bakabilirsiniz.


2

Herhangi bir sorgu optimize ediciyle bir kuruşla .... aynıdırlar.


Herhangi bir gerçek iş yükü ile aynı olmadıklarından oldukça eminim. Neredeyse veriye sahip değilseniz, soru değersizdir.
eKek0

2
Gerçek iş yükü altında kontrol edin. Temel olarak - aynı yürütme planını oluştururlarsa, performans açısından aynıdırlar. En azından normal / basit durumlar için (yani 14 masayı birleştiren değil) aynı olduklarından oldukça eminim;)
TomTom

1

Postgresql'de aynıdır. Bunu biliyoruz çünkü explain analyzeher sorguda yaparsanız , plan aynı çıkıyor. Bu örneği ele alalım:

# explain analyze select e.* from event e join result r on e.id = r.event_id and r.team_2_score=24;

                                                  QUERY PLAN                                                   
---------------------------------------------------------------------------------------------------------------
 Hash Join  (cost=27.09..38.22 rows=7 width=899) (actual time=0.045..0.047 rows=1 loops=1)
   Hash Cond: (e.id = r.event_id)
   ->  Seq Scan on event e  (cost=0.00..10.80 rows=80 width=899) (actual time=0.009..0.010 rows=2 loops=1)
   ->  Hash  (cost=27.00..27.00 rows=7 width=8) (actual time=0.017..0.017 rows=1 loops=1)
         Buckets: 1024  Batches: 1  Memory Usage: 9kB
         ->  Seq Scan on result r  (cost=0.00..27.00 rows=7 width=8) (actual time=0.006..0.008 rows=1 loops=1)
               Filter: (team_2_score = 24)
               Rows Removed by Filter: 1
 Planning time: 0.182 ms
 Execution time: 0.101 ms
(10 rows)

# explain analyze select e.* from event e join result r on e.id = r.event_id where r.team_2_score=24;
                                                  QUERY PLAN                                                   
---------------------------------------------------------------------------------------------------------------
 Hash Join  (cost=27.09..38.22 rows=7 width=899) (actual time=0.027..0.029 rows=1 loops=1)
   Hash Cond: (e.id = r.event_id)
   ->  Seq Scan on event e  (cost=0.00..10.80 rows=80 width=899) (actual time=0.010..0.011 rows=2 loops=1)
   ->  Hash  (cost=27.00..27.00 rows=7 width=8) (actual time=0.010..0.010 rows=1 loops=1)
         Buckets: 1024  Batches: 1  Memory Usage: 9kB
         ->  Seq Scan on result r  (cost=0.00..27.00 rows=7 width=8) (actual time=0.006..0.007 rows=1 loops=1)
               Filter: (team_2_score = 24)
               Rows Removed by Filter: 1
 Planning time: 0.140 ms
 Execution time: 0.058 ms
(10 rows)

Her ikisi de aynı minimum ve maksimum maliyete ve aynı sorgu planına sahiptir. Ayrıca, en üstteki sorguda bile team_score_2'nin bir 'Filtre' olarak uygulandığına dikkat edin.


0

Bu birleşimin yerleştirilmesinin performans için belirleyici faktör olması pek olası değildir. Tsql için yürütme planlamasına yakından aşina değilim, ancak muhtemelen benzer planlara göre otomatik olarak optimize edilecekler.


0

Kural 0: Bazı testler çalıştırın ve görün! Hangisinin daha hızlı olacağını gerçekten söylemenin tek yolu denemektir. Bu tür kıyaslamaların SQL profil oluşturucu kullanılarak gerçekleştirilmesi çok kolaydır.

Ayrıca, hangi farklılıkların öne çıktığını görmek için bir JOIN ve bir WHERE yan tümcesi ile yazılan sorgu için yürütme planını inceleyin.

Son olarak, diğerlerinin de söylediği gibi, bu ikisi, SQL Server'da yerleşik olan da dahil olmak üzere herhangi bir iyi optimize edici tarafından aynı şekilde ele alınmalıdır.


Ancak sadece iç birleşimler için. Dış katılımlar için sonuç kümesi çok farklı olacaktır.
HLGEM

Elbette. Neyse ki, sağlanan örnek iç birleşimleri kullanır.
3Dave

1
Ne yazık ki soru iç birleşimlerle değil, birleşmelerle ilgili.
Paul

Evet David, soru katılımlarla ilgili. Soruyu destekleyen örnek, iç birleşimleri kullanır.
Paul

0

Daha hızlı mı? Deneyin ve görün.

Hangisini okumak daha kolay? Birincisi bana daha "doğru" görünüyor, çünkü taşınan koşul birleşmeyle gerçekten ilgisi yok.


0

Sanırım birincisi, çünkü veriler üzerinde daha spesifik bir filtre oluşturuyor. Ancak , herhangi bir optimizasyonda olduğu gibi yürütme planını da görmelisiniz , çünkü veri boyutuna, sunucu donanımına vb. Bağlı olarak çok farklı olabilir.

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.