SQL Server查询:快速使用文字但速度慢而变量

时间:2010-12-16 09:55:36

标签: sql-server performance sql-server-2008

我有一个视图,使用CTE从表中返回2个整数。如果我查询这样的视图,它会在不到一秒的时间内运行

SELECT * FROM view1 WHERE ID = 1

但是如果我查询这样的视图需要4秒钟。

DECLARE @id INT = 1
SELECT * FROM View1 WHERE ID = @id

我检查了2个查询计划,第一个查询正在主表上执行Clustered index seek返回1条记录,然后将其余的视图查询应用于该结果集,其中第二个查询执行索引扫描返回大约3000条记录而不仅仅是我感兴趣的记录,然后过滤结果集。

我是否有任何明显的缺失,试图让第二个查询使用Index Seek而不是索引扫描。我正在使用SQL 2008,但我所做的任何事情都需要在SQL 2005上运行。起初我认为这是某种参数嗅探问题,但即使清除缓存,我也会得到相同的结果。

8 个答案:

答案 0 :(得分:36)

可能是因为在参数的情况下,优化器无法知道该值不为null,因此它需要创建一个计划,即使它返回正确的结果。如果您有SQL Server 2008 SP1,则可以尝试将OPTION(RECOMPILE)添加到查询中。

答案 1 :(得分:4)

您可以为查询添加hint的OPTIMIZE,例如

DECLARE @id INT = 1
SELECT * FROM View1 WHERE ID = @id OPTION (OPTIMIZE FOR (@ID = 1))

答案 2 :(得分:3)

在我的情况下,DB表列类型被定义为VarChar,并且参数化查询参数类型被定义为NVarChar,这在实际执行计划中引入了CONVERT_IMPLICIT以匹配数据类型,这是母猪的罪魁祸首表现,2秒vs 11秒。只需更正参数类型,使参数化查询与非参数化版本一样快。

希望这可以帮助有类似问题的人。

答案 3 :(得分:1)

当SQL开始使用变量优化查询的查询计划时,它将匹配列的可用索引。在这种情况下有一个索引,所以SQL认为它只会扫描索引寻找值。当SQL使用列和文字值制定查询计划时,它可以查看统计信息和值,以确定它是否应扫描索引或查找是否正确。

使用优化提示和值告诉SQL“这是将在大多数情况下使用的值,因此优化此值”,并存储计划,就好像使用了这个文字值一样。使用优化提示和UNKNOWN的子提示告诉SQL您不知道该值是什么,因此SQL查看列的统计信息并决定什么,搜索或扫描最佳,并相应地制定计划。

答案 4 :(得分:1)

我自己遇到了这个问题,并且运行了<直接分配10ms( WHERE UtilAcctId = 12345 ),但是通过变量赋值( WHERE UtilAcctId = @UtilAcctId )占用了100多倍。
后者的执行计划与我在整个表上运行视图没有什么不同。

我的解决方案不需要大量索引,优化程序提示或长统计信息更新。

相反,我将视图转换为 用户表函数 ,其中参数是WHERE子句所需的值。事实上,这个WHERE子句嵌套了3个深度查询,它仍然有效,并且它回到了< 10ms的速度。

最后我将参数更改为TYPE,它是UtilAcctIds(int)的表。然后我可以将WHERE子句限制为表中的列表。 WHERE UtilAcctId = [parameter-List] .UtilAcctId。 这样做效果更好。我认为用户表函数是预编译的。

答案 5 :(得分:0)

我自己遇到了同样的问题,结果发现这是一个缺少的索引,涉及对子查询结果的(左)连接。

select *
from foo A
left outer join (
  select x, count(*)
  from bar
  group by x
) B on A.x = B.x

为bar.x添加了名为bar_x的索引

答案 6 :(得分:0)

DECLARE @id INT = 1

SELECT * FROM View1 WHERE ID = @id

这样做

DECLARE @sql varchar(max)

SET @sql =' SELECT * FROM View1 WHERE ID =' + CAST(@id as varchar)

EXEC(@sql)

解决您的问题

答案 7 :(得分:0)

我知道这已经很久了,但是我遇到了同样的问题,并且有一个非常简单的解决方案,不需要提示,统计信息更新,其他索引,强制计划等。

基于以上评论“优化器无法知道该值不为null”,我决定将这些值从变量移到表中:

原始代码:

declare @StartTime datetime2(0) = '10/23/2020 00:00:00'
declare @EndTime datetime2(0) = '10/23/2020 01:00:00'
    
SELECT * FROM ...
WHERE 
C.CreateDtTm >= @StartTime
AND  C.CreateDtTm < @EndTime

新代码:

declare @StartTime datetime2(0) = '10/23/2020 00:00:00'
declare @EndTime datetime2(0) = '10/23/2020 01:00:00'

CREATE TABLE #Times (StartTime datetime2(0) NOT NULL, EndTime datetime2(0) NOT NULL)
INSERT INTO #Times(StartTime, EndTime) VALUES(@StartTime, @EndTime)

SELECT * FROM ...
WHERE 
C.CreateDtTm >= (SELECT MAX(StartTime) FROM #Times)
AND  C.CreateDtTm < (SELECT MAX(EndTime) FROM #Times)

这是立即执行的,而不是原始代码的几分钟(显然,结果可能会有所不同)。

我假设如果将主表中的数据类型更改为NOT NULL,它也可以正常工作,但是由于系统限制,我目前无法对此进行测试。