哪个SQL查询更快?根据加入条件或Where子句过滤?


97

比较这两个查询。将筛选器放在连接条件或WHERE子句中是否更快。我一直觉得连接条件比较快,因为它会在最快的时候减少结果集,但是我不确定。

我将构建一些测试以供查看,但我也想就哪些内容也更易于阅读获得意见。

查询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? */

查询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

编辑

我进行了一些测试,结果表明它实际上非常接近,但是该WHERE子句实际上稍微快一点!=)

我完全同意,将过滤器应用于WHERE子句更有意义,我只是对性能影响感到好奇。

已用时间如果标准是: 143016毫秒
已用时间连接标准: 143256毫秒

测试

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
根据数据,WHERE vs JOIN条件可以返回不同的结果集。
OMG小马

3
@OMG Ponies非常正确,但很多时候情况并非如此。
乔恩·埃里克森

2
我不会将差异低于5%称为差异-它们是相同的。您需要2 %%差异的显着性,才能更好地运行测试1000次以确保iti不仅仅是随机的。
TomTom

好处是在加入数据之前对数据进行了过滤,因此如果使用x.ID,则与使用a.ID相比,您更有可能看到改进
MikeT 2013年

Answers:


64

在性能方面,它们是相同的(并产生相同的计划)

从逻辑上讲,如果替换为INNER JOIN,则应该进行有意义的操作LEFT JOIN

在您的情况下,将如下所示:

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

或这个:

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

前者查询不会返回任何实际比赛为a.id比其他1,因此后者的语法(有WHERE)在逻辑上是比较一致的。


当我绘制集合时,我理解了为什么第二种情况更加一致。在前一个查询中,约束a.id = 1仅适用于交叉点,不适用于除交叉点以外的左侧部分。
FtheBuilder

1
在第一个示例中,可能有行a.id != 1,而其他仅行a.id = 1
FtheBuilder

1
您的语言不清楚。“从逻辑上讲,如果...,则应该进行有意义的操作”和“逻辑上更一致”则没有意义。你能改一下吗?
philipxy

23

对于内部联接,放置条件并不重要。SQL编译器将两者都转换为执行计划,在该执行计划中,过滤在联接下方进行(即,好像过滤器表达式出现在联接条件中)。

外部联接是另一回事,因为过滤器的位置改变了查询的语义。


因此,在内部联接中,它首先计算过滤器,然后将过滤器的输出与另一个表联接,还是先联接两个表,然后应用过滤器?
Ashwin 2012年

@Remus Rusanu-您能否详细说明在外连接情况下如何更改语义?基于过滤器的位置,我得到了不同的结果,但无法理解原因
Ananth

3
使用外部联接的@Ananth会为JOIN条件不匹配的联接表的所有列提供NULL。过滤器将不满足NULL并消除行,从而将有效的OUTER连接变为INNER连接。
Remus Rusanu

10

就这两种方法而言。

  • JOIN / ON用于联接表
  • 在哪里过滤结果

虽然您可以不同地使用它们,但对我而言似乎总是有一种气味。

在出现问题时处理性能。然后,您可以研究这种“优化”。


2

对于任何查询优化器来说,它们都是一分钱。


我非常确定,在任何实际工作量下,它们都不相同。如果您几乎没有数据,那么这个问题就毫无价值。
eKek0

2
在实际工作量下进行检查。基本上,如果它们生成相同的执行计划,则它们的性能是相同的。至少对于普通/简单案例(即不是一个连接14个表的案例),我很确定它们是相同的;)
TomTom 2010年

1

在postgresql中,它们是相同的。我们之所以知道这一点,是因为如果您explain analyze对每个查询进行操作,则计划是相同的。举个例子:

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

它们都具有相同的最小和最大成本以及相同的查询计划。另外,请注意,即使在最上面的查询中,team_score_2也会被应用为“过滤器”。


0

实际上,此联接的位置不太可能成为性能的决定因素。我对tsql的执行计划不是很熟悉,但是很可能会将它们自动优化为类似的计划。


0

规则0:运行一些基准测试,看看吧!真正分辨出哪种方法更快的唯一方法是尝试一下。使用SQL事件探查器很容易执行这些类型的基准测试。

另外,检查用JOIN和WHERE子句编写的查询的执行计划,以了解有什么区别。

最后,就像其他人所说的那样,任何一个不错的优化器,包括SQL Server内置的优化器,都应该对这两者进行相同的处理。


但仅适用于内部联接。对于联接,结果集将有很大的不同。
HLGEM 2010年

当然。幸运的是,提供的示例使用内部联接。
3Dave 2010年

1
不幸的是,问题在于联接,而不是内部联接。
保罗

是的,大卫,问题是关于加入的。支持该问题的示例恰好使用内部联接。
保罗

0

它更快吗?试试看。

哪个更容易阅读?对我来说,第一个看起来更“正确”,因为移动的条件与联接实际上无关。


0

我猜是第一个,因为它对数据进行了更具体的过滤。但是您应该看到执行计划,以及任何优化,因为它在数据大小,服务器硬件等方面有很大不同。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.