【发布时间】: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]>=N'2009' AND [database].[dbo].[table2].[time] as [t2].[time]<=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 ON和SET 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 ON和SET 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