【问题标题】:How order of joins affect performance of a query连接顺序如何影响查询的性能
【发布时间】:2011-10-19 10:39:29
【问题描述】:

我的查询在时间性能方面存在很大差异,而且查询中连接(内连接和左外连接)的顺序似乎完全不同。 连接的顺序是否有一些“基本规则”?

它们都是更大查询的一部分。 它们之间的区别在于左连接在较快的查询中放在最后。

慢查询:(> 10 分钟)

SELECT [t0].[Ref], [t1].[Key], [t1].[Name],  
    (CASE 
        WHEN [t3].[test] IS NULL THEN CONVERT(NVarChar(250),@p0)
        ELSE CONVERT(NVarChar(250),[t3].[Key])
     END) AS [value], 
    (CASE 
        WHEN 0 = 1 THEN CONVERT(NVarChar(250),@p1)
        ELSE CONVERT(NVarChar(250),[t4].[Key])
     END) AS [value2]

FROM [dbo].[tblA] AS [t0]
INNER JOIN [dbo].[tblB] AS [t1] ON [t0].[RefB] = [t1].[Ref]

LEFT OUTER JOIN (
    SELECT 1 AS [test], [t2].[Ref], [t2].[Key]
    FROM [dbo].[tblC] AS [t2]
    ) AS [t3] ON [t0].[RefC] = ([t3].[Ref])

INNER JOIN [dbo].[tblD] AS [t4] ON [t0].[RefD] = ([t4].[Ref])

更快的查询:(~ 30 秒)

SELECT [t0].[Ref], [t1].[Key], [t1].[Name],  
    (CASE 
        WHEN [t3].[test] IS NULL THEN CONVERT(NVarChar(250),@p0)
        ELSE CONVERT(NVarChar(250),[t3].[Key])
     END) AS [value], 
    (CASE 
        WHEN 0 = 1 THEN CONVERT(NVarChar(250),@p1)
        ELSE CONVERT(NVarChar(250),[t4].[Key])
     END) AS [value2]

FROM [dbo].[tblA] AS [t0]
INNER JOIN [dbo].[tblB] AS [t1] ON [t0].[RefB] = [t1].[Ref]

INNER JOIN [dbo].[tblD] AS [t4] ON [t0].[RefD] = ([t4].[Ref])

LEFT OUTER JOIN (
    SELECT 1 AS [test], [t2].[Ref], [t2].[Key]
    FROM [dbo].[tblC] AS [t2]
    ) AS [t3] ON [t0].[RefC] = ([t3].[Ref])

【问题讨论】:

  • 查询性能优化往往因 SQL 风格而异。这是 SQLServer,还是另一种类型的 SQL(MySQL、PostgresQL 等)?
  • 您可能会发现您遇到了参数嗅探问题。更改文本将意味着不会使用相同的缓存计划。

标签: sql sql-server linq join


【解决方案1】:

通常 INNER JOIN 顺序无关紧要,因为内部联接是可交换和关联的。在这两种情况下,您仍然拥有t0 inner join t4,所以应该没有区别。

重新表述,SQL 是声明性的:你说的是“你想要什么”,而不是“如何”。优化器按照“如何”工作,并将根据需要重新排序 JOIN,在实践中也像 WHERE 等一样。

在复杂的查询中,基于成本的查询优化器不会耗尽所有排列,因此它有时可能很重要。

所以,我会检查这些:

  • 您说这些是更大查询的一部分,所以这部分不太重要,因为整个查询都很重要。
  • 如果任何表实际上是视图,也可以使用视图隐藏复杂性
  • 无论以何种顺序代码运行,这是否可重复?
  • 查询计划有何不同?

查看其他一些 SO 问题:

【讨论】:

    【解决方案2】:

    如果您有 2 个以上的表,则订购表连接很重要。它可以产生很大的不同。第一个表应该得到一个领先的提示。第一个表是具有最多选择性行的对象。例如:如果您有一个包含 1.000.000 人的成员表,并且您只想选择女性性别并且它是第一个表,那么您只需将 500.000 条记录连接到下一个表。如果此表位于连接顺序的末尾(可能是表 4,5 或 6),则将连接每条记录(最坏情况为 1.000.000)。这包括内连接和外连接。

    规则:从最有选择性的表开始,然后加入下一个逻辑上最有选择性的表。

    转换功能和美化应该最后做。有时最好 将 shole SQL 捆绑在括号中,并在外部 select 语句中使用表达式和函数。

    【讨论】:

      【解决方案3】:

      在左连接的情况下,它会影响很多性能。我在这样的选择查询中遇到了问题:

      select distinct count(p0_.id) over ()        as col_0_0_,
             p0_.id                       as col_1_0_,
             p0_.lp           as col_2_0_,
             0
                                                as col_3_0_,
             max(coalesce(i6_.cft, i7_.rfo,
                          ''))                  as col_4_0_,
             p0_.pdv              as col_5_0_,
             (s8_.qer)
                                                as col_6_0_,
             cf1_.ests as col_7_0_
      from Produit p0_
               left outer join CF cf1_ on p0_.fk_cf = cf1_.id
               left outer join CA c2_ on cf1_.fk_ca = c2_.id
               left outer join ml mt on c2_.fk_m = mt.id
               left outer join sk s8_ on p0_.id = s8_.fk_p
               left outer join rf r5_ on
              rp4_.fk_r = r5_.id
               left outer join
           in i6_ on r5_.fk_ireftc = i6_.id
               left outer join r_p_r rp4_ on p0_.id = rp4_.fk_p
               left outer join
           ir i7_ on r5_.fk_if = i7_.id
            left outer join re_p_g gc9_ on p0_.id = gc9_.fk_p
               left outer join gc g10_ on gc9_.fk_g = g10_.id
      where
        and (p0_.lC is null or p0_.lS = 'E')
        and g10_.id is null or g10_.id
        and r5_.fk_i is null
      group by col_1_0_, col_2_0_, col_3_0_, col_5_0_, col_6_0_, col_7_0_
      order by col_2_0_ asc, p0_.id
      limit 10;
      

      查询需要 13 到 15 秒才能执行,当我更改顺序时,它需要 1 到 2 秒。

      select distinct count(p0_.id) over ()        as col_0_0_,
             p0_.id                       as col_1_0_,
             p0_.lp           as col_2_0_,
             0
                                                as col_3_0_,
             max(coalesce(i6_.cft, i7_.rfo,
                          ''))                  as col_4_0_,
             p0_.pdv              as col_5_0_,
             (s8_.qer)
                                                as col_6_0_,
             cf1_.ests as col_7_0_
      from Produit p0_
               left outer join CF cf1_ on p0_.fk_cf = cf1_.id
               left outer join sk s8_ on p0_.id = s8_.fk_p
               left outer join r_p_r rp4_ on p0_.id = rp4_.fk_p
               left outer join re_p_g gc9_ on p0_.id = gc9_.fk_p
               left outer join CA c2_ on cf1_.fk_ca = c2_.id
               left outer join ml mt on c2_.fk_m = mt.id
               left outer join rf r5_ on
              rp4_.fk_r = r5_.id
               left outer join
           in i6_ on r5_.fk_ireftc = i6_.id
               left outer join
           ir i7_ on r5_.fk_if = i7_.id
               left outer join gc g10_ on gc9_.fk_g = g10_.id
      where
        and (p0_.lC is null or p0_.lS = 'E')
        and(g10_.id is null
        and r5_.fk_i is null
      group by col_1_0_, col_2_0_, col_3_0_, col_5_0_, col_6_0_, col_7_0_
      order by col_2_0_ asc, p0_.id
      limit 10;
      

      在我的情况下,我会更改顺序,以防我在加载表时使用在随后的连接中使用该表的所有连接,而不是将其加载到另一个块中。就像在我的 p0_ 表中一样,我在前 4 行中进行了所有左连接,而不像在第一个代码中那样。

      PS:为了在 postgre 中测试我的性能,我使用这个网站:http://tatiyants.com/pev/#/plans/new

      【讨论】:

        【解决方案4】:

        至少在 SQLite 中,我发现它有很大的不同。实际上,它不需要是一个非常复杂的查询来显示差异。然而,我的 JOIN 语句位于嵌入子句中。

        正如 Christian 所指出的,基本上,您应该首先从最具体的限制开始。

        【讨论】:

          猜你喜欢
          • 2019-01-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-02-05
          • 1970-01-01
          • 1970-01-01
          • 2011-01-07
          • 1970-01-01
          相关资源
          最近更新 更多