简体   繁体   English

SQL Server 查询:字面量快,变量慢

[英]SQL Server Query: Fast with Literal but Slow with Variable

I have a view that returns 2 ints from a table using a CTE.我有一个使用 CTE 从表中返回 2 个整数的视图。 If I query the view like this it runs in less than a second如果我像这样查询视图,它会在不到一秒的时间内运行

SELECT * FROM view1 WHERE ID = 1

However if I query the view like this it takes 4 seconds.但是,如果我像这样查询视图需要 4 秒。

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

I've checked the 2 query plans and the first query is performing a Clustered index seek on the main table returning 1 record then applying the rest of the view query to that result set, where as the second query is performing an index scan which is returning about 3000 records records rather than just the one I'm interested in and then later filtering the result set.我检查了 2 个查询计划,第一个查询正在主表上执行聚集索引查找,返回 1 条记录,然后将视图查询的其余部分应用于该结果集,其中第二个查询正在执行索引扫描返回大约 3000 条记录,而不仅仅是我感兴趣的记录,然后再过滤结果集。

Is there anything obvious that I'm missing to try to get the second query to use the Index Seek rather than an index scan.在尝试让第二个查询使用索引查找而不是索引扫描时,我是否遗漏了任何明显的东西。 I'm using SQL 2008 but anything I do needs to also run on SQL 2005. At first I thought it was some sort of parameter sniffing problem but I get the same results even if I clear the cache.我正在使用 SQL 2008,但我所做的任何事情都需要在 SQL 2005 上运行。起初我认为这是某种参数嗅探问题,但即使我清除了缓存,我也得到了相同的结果。

Probably it is because in the parameter case, the optimizer cannot know that the value is not null, so it needs to create a plan that returns correct results even when it is.可能是因为在参数情况下,优化器无法知道该值不为空,因此需要创建一个即使是也返回正确结果的计划。 If you have SQL Server 2008 SP1 you can try adding OPTION(RECOMPILE) to the query.如果您有 SQL Server 2008 SP1,您可以尝试将OPTION(RECOMPILE)添加到查询中。

You could add an OPTIMIZE FOR hint to your query, eg您可以在查询中添加 OPTIMIZE FOR 提示,例如

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

In my case in DB table column type was defined as VarChar and in parameterized query parameter type was defined as NVarChar, this introduced CONVERT_IMPLICIT in the actual execution plan to match data type before comparing and that was culprit for sow performance, 2 sec vs 11 sec.在我的情况下,DB 表中的列类型定义为 VarChar,参数化查询参数类型定义为 NVarChar,这在实际执行计划中引入了CONVERT_IMPLICIT以在比较之前匹配数据类型,这是母猪性能的罪魁祸首,2 秒与 11 秒. Just correcting parameter type made parameterized query as fast as non parameterized version.只需更正参数类型即可使参数化查询与非参数化版本一样快。

Hope this may help someone with similar issue.希望这可以帮助有类似问题的人。

I ran into this problem myself with a view that ran < 10ms with a direct assignment ( WHERE UtilAcctId=12345 ), but took over 100 times as long with a variable assignment ( WHERE UtilAcctId = @UtilAcctId ).我自己遇到了这个问题,该视图使用直接赋值( WHERE UtilAcctId=12345 )运行 < 10 毫秒,但使用变量赋值( WHERE UtilAcctId = @UtilAcctId )花费了 100 多倍的时间。
The execution-plan for the latter was no different than if I had run the view on the entire table.后者的执行计划与我在整个表上运行视图没有什么不同。

My solution didn't require tons of indexes, optimizer-hints, or a long-statistics-update.我的解决方案不需要大量的索引、优化器提示或长时间的统计更新。

Instead I converted the view into a User-Table-Function where the parameter was the value needed on the WHERE clause.相反,我将视图转换为User-Table-Function ,其中参数是 WHERE 子句所需的值。 In fact this WHERE clause was nested 3 queries deep and it still worked and it was back to the < 10ms speed.事实上,这个 WHERE 子句嵌套了 3 个查询深度,它仍然有效,并且恢复到 < 10ms 的速度。

Eventually I changed the parameter to be a TYPE that is a table of UtilAcctIds (int).最终,我将参数更改为 TYPE,即 UtilAcctIds (int) 表。 Then I can limit the WHERE clause to a list from the table.然后我可以将 WHERE 子句限制为表中的列表。 WHERE UtilAcctId = [parameter-List].UtilAcctId. WHERE UtilAcctId = [参数列表].UtilAcctId。 This works even better.这效果更好。 I think the user-table-functions are pre-compiled.我认为用户表函数是预编译的。

When SQL starts to optimize the query plan for the query with the variable it will match the available index against the column.当 SQL 开始使用变量优化查询的查询计划时,它将根据列匹配可用索引。 In this case there was an index so SQL figured it would just scan the index looking for the value.在这种情况下,有一个索引,所以 SQL 认为它只会扫描索引以查找值。 When SQL made the plan for the query with the column and a literal value it could look at the statistics and the value to decide if it should scan the index or if a seek would be correct.当 SQL 使用列和文字值为查询制定计划时,它可以查看统计信息和值来决定是否应该扫描索引或查找是否正确。

Using the optimize hint and a value tells SQL that “this is the value which will be used most of the time so optimize for this value” and a plan is stored as if this literal value was used.使用优化提示和一个值告诉 SQL “这是大部分时间将使用的值,因此针对该值进行优化”,并且计划被存储,就好像使用了这个文字值一样。 Using the optimize hint and the sub-hint of UNKNOWN tells SQL you do not know what the value will be, so SQL looks at the statistics for the column and decides what, seek or scan, will be best and makes the plan accordingly.使用优化提示和 UNKNOWN 的子提示告诉 SQL 您不知道该值将是什么,因此 SQL 会查看列的统计信息并决定搜索或扫描什么是最佳的,并相应地制定计划。

Came across this same issue myself and it turned out to be a missing index involving a (left) join on the result of a subquery.我自己也遇到了同样的问题,结果是缺少索引,涉及子查询结果的(左)连接。

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

Added an index named bar_x for bar.x为 bar.x 添加了名为 bar_x 的索引

I know this is long since answered, but I came across this same issue and have a fairly simple solution that doesn't require hints, statistics-updates, additional indexes, forcing plans etc.我知道这已经很久了,但我遇到了同样的问题,并且有一个相当简单的解决方案,不需要提示、统计更新、附加索引、强制计划等。

Based on the comment above that "the optimizer cannot know that the value is not null", I decided to move the values from a variable into a table:基于上面的评论“优化器无法知道该值不为空”,我决定将值从变量移动到表中:

Original Code:原始代码:

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

New Code:新代码:

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)

This performed instantly as opposed to several minutes for the original code (obviously your results may vary) .与原始代码的几分钟相比,这立即执行(显然您的结果可能会有所不同)。

I assume if I changed my data type in my main table to be NOT NULL, it would work as well, but I was not able to test this at this time due to system constraints.我假设如果我将主表中的数据类型更改为 NOT NULL,它也可以正常工作,但是由于系统限制,我目前无法对此进行测试。

DECLARE @id INT = 1

SELECT * FROM View1 WHERE ID = @id

Do this这样做

DECLARE @sql varchar(max)

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

EXEC (@sql)

Solves your problem解决您的问题

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM