【问题标题】:Which SQL query is faster? Filter on Join criteria or Where clause?哪个 SQL 查询更快?过滤加入条件或 Where 子句?
【发布时间】:2011-01-31 09:15:42
【问题描述】:

比较这两个查询。将过滤器放在连接条件或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

【问题讨论】:

  • 根据数据,WHERE vs JOIN 标准可以返回不同的结果集。
  • @OMG Ponies 非常正确,但很多时候情况并非如此。
  • 我不会将低于 5% 的差异称为差异——它们是相同的。您希望 2%% 差异的显着性更好地运行测试 1000 次,以确保它不仅仅是随机的。
  • 好处是在加入之前过滤数据,所以如果它是 x.ID,那么你会比使用 a.ID 更有可能看到改进

标签: sql sql-server tsql sql-server-2008


【解决方案1】:

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

从逻辑上讲,如果将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

前一个查询不会返回除1 之外的任何a.id 的实际匹配项,因此后一个语法(与WHERE)在逻辑上更加一致。

【讨论】:

  • 当我绘制集合时,我明白为什么第二种情况更一致。在前一个查询中,约束a.id = 1仅适用于交叉点,而不适用于不包括交叉点的左侧部分。
  • 在第一个示例中,可能有a.id != 1 所在的行,另一个只有a.id = 1 所在的行。
  • 您的语言不清楚。 “从逻辑上讲,如果……,您应该使仍然有意义的操作”和“逻辑上更一致”没有意义。你能改写一下吗?
【解决方案2】:

对于内部联接,您将标准放在哪里并不重要。 SQL 编译器会将两者转换为执行计划,在该执行计划中,过滤发生在连接下方(即,好像过滤器表达式出现在连接条件中)。

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

【讨论】:

  • 所以在内部连接中,它首先计算过滤器,然后将过滤器的输出与另一个表连接起来,还是先连接两个表,然后再应用过滤器?
  • @Remus Rusanu - 您能否详细说明在 Outer-join 的情况下语义如何变化?我根据过滤器的位置得到不同的结果,但无法理解原因
  • @Ananth 使用外连接,对于连接条件不匹配的连接表的所有列,您将获得 NULL。过滤器将不满足 NULL 并消除行,从而将有效的 OUTER 连接变为 INNER 连接。
  • @Ananth 根据您的评论,我实现了所需的优化。我的更改是从 WHERE x.TableAID = a.ID 或 x.TableAID 为空到 ON x.TableAID = a.ID。在 OUTER 连接上更改过滤器的位置让编译器知道先过滤后连接,而不是先连接后过滤。它还能够使用该列上的索引,因为它不必匹配 Null。查询响应从 61 秒更改为 2 秒。
【解决方案3】:

就这两种方法而言。

  • JOIN/ON 用于连接表
  • WHERE 用于过滤结果

虽然你可以用不同的方式使用它们,但对我来说它总是一种气味。

在出现问题时处理性能。然后你可以研究这样的“优化”。

【讨论】:

  • 这似乎无法回答问题。
【解决方案4】:

任何查询优化器都值一分钱......它们是相同的。

【讨论】:

  • 我很确定,对于任何实际工作量,它们都不相同。如果你几乎没有数据,那么这个问题毫无价值。
  • 在实际工作负载下进行检查。基本上 - 如果它们生成相同的执行计划,它们......在性能上是相同的。至少对于正常/简单的情况(即不是加入 14 个表的那个)我很确定它们是相同的;)
【解决方案5】:

在 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 也被应用为“过滤器”。

【讨论】:

    【解决方案6】:

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

    【讨论】:

      【解决方案7】:

      规则 #0:运行一些基准测试,看看!真正判断哪个更快的唯一方法是尝试它。使用 SQL 分析器很容易执行这些类型的基准测试。

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

      最后,正如其他人所说,任何体面的优化器都应该同等对待这两者,包括 SQL Server 中内置的优化器。

      【讨论】:

      • 但仅适用于内部连接。输出连接的结果集将非常不同。
      • 当然。幸运的是,提供的示例使用了内部连接。
      • 不幸的是,问题是关于联接,而不是内部联接。
      • 是的,大卫,问题是关于连接的。支持该问题的示例恰好使用了内部连接。
      【解决方案8】:

      更快吗?试试看。

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

      【讨论】:

        【解决方案9】:

        我猜是第一个,因为它对数据进行了更具体的过滤。但是你should see the execution plan,就像任何优化一样,因为它可能会因数据大小、服务器硬件等而大不相同。

        【讨论】:

          猜你喜欢
          • 2023-03-27
          • 2010-11-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-03-17
          • 1970-01-01
          相关资源
          最近更新 更多