【问题标题】:Query time with a JOIN using a subquery versus a string使用子查询与字符串的 JOIN 查询时间
【发布时间】:2011-03-27 05:17:06
【问题描述】:

我有一个这样的查询:

SELECT * FROM table1 t1
JOIN table2 t2 ON t1.id = t2.id 
     AND t2.time IN (SELECT year FROM activeYears WHERE active = 1)

其中 activeYears.year 是 NVARCHAR(50),每一年都是一行。

为什么这个连接的运行时间比这个查询快:

SELECT * FROM table1 t1
JOIN table2 t2 ON t1.id = t2.id AND t2.time IN ('2009','2010')

这是一个简短、简单的版本,基本上我有一个带有使用子查询的连接的大型查询。当我将子查询更改为字符串进行测试时,即使清除缓存也需要双倍的运行时间。我认为这可能是一个转换问题,但我尝试将两个变量也声明为 NVARCHAR(50),并在查询中使用它们,但没有任何区别。

这几天让我感到困惑,我不明白为什么子查询更快,除非查询实际上以某种方式构建不同。

谢谢!


edit -- 执行计划信息

我对这两个执行计划进行了比较,并将尝试为您提供匿名的亮点。

更快(子查询)查询的执行计划的 MissingIndexes 部分:

         <QueryPlan CachedPlanSize="196" CompileTime="2166" CompileCPU="2166" CompileMemory="18640">
            <MissingIndexes>
             <MissingIndexGroup Impact="41.4663">
                <MissingIndex Database="[database]" Schema="[dbo]" Table="[table2]">
                 <ColumnGroup Usage="EQUALITY">
                   <Column Name="[time]" ColumnId="3" />
                    <Column Name="[id]" ColumnId="10" />
                  </ColumnGroup>
                </MissingIndex>
              </MissingIndexGroup>
            </MissingIndexes>

查询的 MissingIndexes 部分使用字符串表示 t2.time IN ('2009','2010')

           <MissingIndexes>
              <MissingIndexGroup Impact="35.4994">
                <MissingIndex Database="[database]" Schema="[dbo]" Table="[table2]">
                  <ColumnGroup Usage="INEQUALITY">
                    <Column Name="[time]" ColumnId="3" />
                  </ColumnGroup>
                  <ColumnGroup Usage="INCLUDE">
                    <Column Name="[id]" ColumnId="10" />
                    <Column Name="[field1]" ColumnId="15" />
                  </ColumnGroup>
                </MissingIndex>
              </MissingIndexGroup>
              <MissingIndexGroup Impact="44.364">
                <MissingIndex Database="[database]" Schema="[dbo]" Table="[table2]">
                  <ColumnGroup Usage="EQUALITY">
                    <Column Name="[time]" ColumnId="3" />
                    <Column Name="[id]" ColumnId="10" />
                  </ColumnGroup>
                  <ColumnGroup Usage="INCLUDE">
                    <Column Name="[field1]" ColumnId="15" />
                  </ColumnGroup>
                </MissingIndex>
              </MissingIndexGroup>
            </MissingIndexes>

然后在每个计划中都有一个不同的嵌套循环,这是完全不同的嵌套循环,按照与上面相同的顺序,首先是子查询,然后是字符串版本:

                <RelOp AvgRowSize="878" EstimateCPU="4.45867E-06" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="1.06667" LogicalOp="Left Outer Join" NodeId="1" Parallel="false" PhysicalOp="Nested Loops" EstimatedTotalSubtreeCost="4.44321">
                  <OutputList>
                     --SNIPPED--
                  </OutputList>
                  <NestedLoops Optimized="false">
                    <OuterReferences>
                      <ColumnReference Database="[database]" Schema="[dbo]" Table="[table1]" Alias="[t1]" Column="time" />
                      <ColumnReference Database="[database]" Schema="[dbo]" Table="[table1]" Alias="[t1]" Column="id" />
                    </OuterReferences>

相同的嵌套循环,但来自第二个查询:

                <RelOp AvgRowSize="878" EstimateCPU="0.223308" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="1.06667" LogicalOp="Left Outer Join" NodeId="1" Parallel="false" PhysicalOp="Nested Loops" EstimatedTotalSubtreeCost="4.66781">
                  <OutputList>
                    -- SNIPPED --
                  </OutputList>
                  <NestedLoops Optimized="false">
                    <Predicate>
                      <ScalarOperator ScalarString="[database].[dbo].[table1].[time] as [t1].[time]=[database].[dbo].[table2].[time] as [t2].[time] AND [database].[dbo].[table1].[id] as [t1].[id]=[database].[dbo].[table2].[id] as [t2].[id]">
                        <Logical Operation="AND">
                          <ScalarOperator>
                            <Compare CompareOp="EQ">
                              <ScalarOperator>
                                <Identifier>
                                  <ColumnReference Database="[database]" Schema="[dbo]" Table="[table1]" Alias="[t1]" Column="time" />
                                </Identifier>
                              </ScalarOperator>
                              <ScalarOperator>
                                <Identifier>
                                  <ColumnReference Database="[database]" Schema="[dbo]" Table="[table2]" Alias="[t2]" Column="time" />
                                </Identifier>
                              </ScalarOperator>
                            </Compare>
                          </ScalarOperator>
                          <ScalarOperator>
                            <Compare CompareOp="EQ">
                              <ScalarOperator>
                                <Identifier>
                                  <ColumnReference Database="[database]" Schema="[dbo]" Table="[table1]" Alias="[t1]" Column="id" />
                                </Identifier>
                              </ScalarOperator>
                              <ScalarOperator>
                                <Identifier>
                                  <ColumnReference Database="[database]" Schema="[dbo]" Table="[table2]" Alias="[t2]" Column="id" />
                                </Identifier>
                              </ScalarOperator>
                            </Compare>
                          </ScalarOperator>
                        </Logical>
                      </ScalarOperator>
                    </Predicate>

最后,同样的顺序,还有另一个不同的嵌套循环,看起来可能会有所不同,因为它似乎显示了查询的那部分是如何构建的,首先是子查询计划的版本:

<ScalarOperator ScalarString="[database].[dbo].[table1].[id] as [t1].[id]=[database].[dbo].[table2].[id] as [t2].[id] AND [database].[dbo].[table1].[time] as [t1].[time]=[database].[dbo].[table2].[time] as [t2].[time] AND [database].[dbo].[table2].[time] as [t2].[time]&gt;=N'2009' AND [database].[dbo].[table2].[time] as [t2].[time]&lt;=N'2010'"> 

然后是字符串版本,IN('2009', '2010'):

<ScalarOperator ScalarString="[database].[dbo].[table2].[time] as [t2].[time]=N'2009' OR [database].[dbo].[table2].[time] as [t2].[time]=N'2010'">

第二次编辑——统计信息

每个请求,这里是SET STATISTICS TIME ONSET STATISTICS IO ON,顺序同上,先子查询:

Table 'activeYear'. Scan count 2, logical reads 2010, physical reads 2, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'table2'. Scan count 1, logical reads 2339848, physical reads 0, read-ahead reads 2303, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'table3'. Scan count 1016, logical reads 4624, physical reads 21, read-ahead reads 1047, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 12, logical reads 109, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'table4'. Scan count 1, logical reads 126, physical reads 0, read-ahead reads 126, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'table1'. Scan count 1033, logical reads 5331, physical reads 57, read-ahead reads 123, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'table5'. Scan count 1, logical reads 219, physical reads 0, read-ahead reads 219, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'table6'. Scan count 1, logical reads 2, physical reads 2, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:
   CPU time = 10328 ms,  elapsed time = 11479 ms.

然后是字符串

Table 'table2'. Scan count 1, logical reads 2339848, physical reads 0, read-ahead reads 2303, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'table3'. Scan count 1016, logical reads 4467, physical reads 21, read-ahead reads 1047, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 659, logical reads 5863, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'table4'. Scan count 1, logical reads 126, physical reads 0, read-ahead reads 126, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'table1'. Scan count 1033, logical reads 5228, physical reads 60, read-ahead reads 120, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'table5'. Scan count 1, logical reads 219, physical reads 0, read-ahead reads 219, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'activeYear'. Scan count 1, logical reads 2, physical reads 2, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'table6'. Scan count 1, logical reads 2, physical reads 2, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:
   CPU time = 16719 ms,  elapsed time = 17447 ms.

(这个连接中的表比简化查询多一些,但我关心的只是单个连接上的字符串与子查询的变化......所以希望这是孤立的,因为 t1 分别连接到此查询中的所有表。)

【问题讨论】:

  • 您比较过这些计划吗?您是否使用SET STATISTICS IO ONSET STATISTICS TIME ON 进行了测试?我猜第二个欺骗查询优化器从 t2.time 开始(认为它更小)。第一个看起来会从 t1 开始,然后加入 t2,因为 IN(子查询)通常更昂贵。
  • 可能有很多原因,检查查询计划以获得提示。一种猜测是前者可以使用后者不能使用的索引。
  • 如果你执行会得到什么:SELECT * FROM table1 t1 JOIN table2 t2 ON t1.id = t2.id WHERE t2.time IN ('2009','2010')
  • @Mitch 这样更快。实际上,当我这样做时,它会反转。将 WHERE t2.time IN ('2009','2010') 成本更高,但运行速度更快 将 WHERE t2.time IN (SELECT year FROM activeYears WHERE active = 1) 成本更低,但返回结果更慢,因为好吧。
  • @Richard 我按照你的建议添加了一些统计数据。我不确定工作台是什么,但它似乎是最大的不同,除非我错过了更重要的东西?

标签: sql sql-server-2005 join


【解决方案1】:

有根据的猜测:第一个查询的 IN 被转换为 JOIN:

SELECT * FROM table1 t1
JOIN table2 t2 ON t1.id = t2.id 
JOIN activeYears ay ON t2.time = ay.year 
WHERE ay.active = 1

当 2d 查询的 IN 转换为 OR(甚至 UNION)时:

SELECT * FROM table1 t1
JOIN table2 t2 ON t1.id = t2.id 
WHERE t2.time = '2009'
   OR t2.time = '2010'

【讨论】:

  • 考虑到这些 ScalarOperator 标签,这是有道理的,我看到使用了 OR。但是在更快的查询中,它说 [t2].[time]>=N'2009' AND [t2].[time]<=N'2010' ...它不可能知道这些是唯一的那里有两个可能的值,我只能假设它使用索引来知道这两个 nvarchars 之间没有值会被该查询错误地拾取? (我可能会得出结论,它只知道 2009 年和 2010 年之间什么都没有,但实际字段与此略有不同,甚至不是数字。更像是“1982-1985”和“1986-2000”。)跨度>
猜你喜欢
  • 2021-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-07
  • 2021-07-08
  • 1970-01-01
  • 2021-10-14
  • 2021-01-14
相关资源
最近更新 更多