Welche SQL-Abfrage ist schneller? Nach Beitrittskriterien oder Where-Klausel filtern?


97

Vergleichen Sie diese beiden Abfragen. Ist es schneller, den Filter auf die Join-Kriterien oder in die WHEREKlausel zu setzen ? Ich hatte immer das Gefühl, dass es bei den Join-Kriterien schneller ist, weil es die Ergebnismenge zum schnellstmöglichen Zeitpunkt reduziert, aber ich weiß es nicht genau.

Ich werde einige Tests erstellen, um zu sehen, aber ich wollte auch Meinungen dazu einholen, welche auch klarer zu lesen sind.

Abfrage 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? */

Abfrage 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

BEARBEITEN

Ich habe einige Tests durchgeführt und die Ergebnisse zeigen, dass es tatsächlich sehr eng ist, aber die WHEREKlausel ist tatsächlich etwas schneller! =)

Ich stimme absolut zu, dass es sinnvoller ist, den Filter auf die WHEREKlausel anzuwenden. Ich war nur neugierig auf die Auswirkungen auf die Leistung.

ELAPSED TIME WHERE CRITERIA: 143016 ms
ELAPSED TIME JOIN CRITERIA: 143256 ms

PRÜFUNG

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

9
Abhängig von den Daten können die Kriterien WHERE vs JOIN unterschiedliche Ergebnismengen zurückgeben.
OMG Ponys

3
@OMG Ponys sehr wahr, aber oft nicht so gut.
Jon Erickson

2
Ich würde Differenz nicht unter 5% als Differenz bezeichnen - sie sind gleich. Sie möchten eine Signifikanz für einen Unterschied von 2 %%, führen Sie die Tests besser 1000 Mal durch, um sicherzustellen, dass sie nicht nur zufällig sind.
TomTom

Der Vorteil besteht darin, dass die Daten vor dem Beitritt gefiltert werden. Wenn es sich also um x.ID handelt, ist die Wahrscheinlichkeit einer Verbesserung höher als bei einer a.ID
MikeT

Antworten:


64

In Bezug auf die Leistung sind sie gleich (und produzieren die gleichen Pläne)

Logischerweise sollten Sie die Operation ausführen, die immer noch sinnvoll ist, wenn Sie sie durch INNER JOINeine ersetzen LEFT JOIN.

In Ihrem Fall sieht dies folgendermaßen aus:

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

oder dieses:

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

Die erstere Abfrage gibt keine tatsächlichen Übereinstimmungen für a.idandere als zurück 1, sodass die letztere Syntax (mit WHERE) logisch konsistenter ist.


Als ich die Mengen zeichnete, verstand ich, warum der zweite Fall konsistenter ist. In der vorherigen Abfrage gilt die Einschränkung a.id = 1nur für die Kreuzung, nicht für den linken Teil ohne die Kreuzung.
FtheBuilder

1
Im ersten Beispiel kann es Zeilen geben, in denen a.id != 1das andere nur Zeilen enthält, in denen a.id = 1.
FtheBuilder

1
Ihre Sprache ist unklar. "Logischerweise sollten Sie die Operation machen, die immer noch Sinn macht, wenn ..." und "logisch konsistenter" keinen Sinn ergeben. Können Sie bitte umformulieren?
Philipxy

23

Bei inneren Verknüpfungen spielt es keine Rolle, wo Sie Ihre Kriterien angeben. Der SQL-Compiler wandelt beide in einen Ausführungsplan um, in dem die Filterung unterhalb des Joins erfolgt (dh als ob die Filterausdrücke in der Join-Bedingung erscheinen).

Äußere Verknüpfungen sind eine andere Sache, da der Ort des Filters die Semantik der Abfrage ändert.


Bei inneren Verknüpfungen wird also zuerst der Filter berechnet und dann die Ausgabe des Filters mit der anderen Tabelle verknüpft. Oder werden zuerst die beiden Tabellen verknüpft und dann der Filter angewendet?
Ashwin

@Remus Rusanu - könnten Sie bitte erläutern, wie sich die Semantik im Falle eines Outer-Joins ändert? Ich erhalte unterschiedliche Ergebnisse basierend auf der Position des Filters, kann aber nicht verstehen warum
Ananth

3
@Ananth mit einem äußeren Join erhalten Sie NULL-Werte für alle Spalten der verknüpften Tabelle, in denen die JOIN-Bedingung nicht übereinstimmt. Filter erfüllen nicht den NULL-Wert und entfernen die Zeilen, wodurch der OUTER-Join in einen INNER-Join umgewandelt wird.
Remus Rusanu

10

Soweit die beiden Methoden gehen.

  • JOIN / ON dient zum Verbinden von Tabellen
  • WO dient zum Filtern von Ergebnissen

Obwohl Sie sie anders verwenden können, scheint es mir immer ein Geruch zu sein.

Beschäftige dich mit Leistung, wenn es ein Problem ist. Dann können Sie sich solche "Optimierungen" ansehen.


2

Mit jedem Abfrageoptimierer haben sie einen Cent ... sie sind identisch.


Ich bin mir ziemlich sicher, dass sie bei jeder tatsächlichen Arbeitsbelastung nicht identisch sind. Wenn Sie fast keine Daten haben, ist die Frage wertlos.
eKek0

2
Überprüfen Sie es unter realer Arbeitsbelastung. Grundsätzlich gilt: Wenn sie denselben Ausführungsplan erstellen, sind sie ... in der Leistung identisch. Zumindest für normale / einfache Fälle (dh nicht für diejenigen, die 14 Tische verbinden) bin ich mir ziemlich sicher, dass sie identisch sind;)
TomTom

1

In postgresql sind sie gleich. Wir wissen das, denn wenn Sie dies explain analyzebei jeder der Abfragen tun , ist der Plan derselbe. Nehmen Sie dieses Beispiel:

# 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)

Beide haben die gleichen minimalen und maximalen Kosten sowie den gleichen Abfrageplan. Beachten Sie außerdem, dass team_score_2 auch in der obersten Abfrage als 'Filter' angewendet wird.


0

Es ist wirklich unwahrscheinlich, dass die Platzierung dieses Joins der entscheidende Faktor für die Leistung ist. Ich bin mit der Ausführungsplanung für tsql nicht vertraut, aber es ist wahrscheinlich, dass sie automatisch für ähnliche Pläne optimiert werden.


0

Regel Nr. 0: Führen Sie einige Benchmarks durch und sehen Sie! Der einzige Weg, um wirklich zu sagen, was schneller sein wird, besteht darin, es zu versuchen. Diese Arten von Benchmarks sind mit dem SQL-Profiler sehr einfach durchzuführen.

Überprüfen Sie auch den Ausführungsplan für die Abfrage, die mit einer JOIN- und einer WHERE-Klausel geschrieben wurde, um festzustellen, welche Unterschiede auffallen.

Schließlich sollten diese beiden, wie andere bereits gesagt haben, von jedem anständigen Optimierer, einschließlich des in SQL Server integrierten Optimierers, identisch behandelt werden.


Aber nur für innere Verbindungen. Die Ergebnismenge ist für Out-Joins sehr unterschiedlich.
HLGEM

Natürlich. Glücklicherweise verwendet das bereitgestellte Beispiel innere Verknüpfungen.
3Dave

1
Leider geht es um Joins, nicht um innere Joins.
Paul

Ja David, die Frage betrifft Joins. Das Beispiel, das die Frage unterstützt, verwendet zufällig innere Verknüpfungen.
Paul

0

Ist es schneller Probieren Sie es aus und sehen Sie.

Welches ist leichter zu lesen? Das erste sieht für mich "korrekter" aus, da die verschobene Bedingung nichts wirklich mit dem Join zu tun hat.


0

Ich denke, das erste, weil es einen spezifischeren Filter über die Daten macht. Sie sollten den Ausführungsplan jedoch wie bei jeder Optimierung sehen, da er je nach Datengröße, Serverhardware usw. sehr unterschiedlich sein kann.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.