2009-11-03 79 views
6

从参数化查询更改为非参数化查询时,我无法理解SQL Server中我的语句的估计查询计划的行为。SQL Server查询计划差异

我有以下查询:

DECLARE @p0 UniqueIdentifier = '1fc66e37-6eaf-4032-b374-e7b60fbd25ea' 
SELECT [t5].[value2] AS [Date], [t5].[value] AS [New] 
FROM (
    SELECT COUNT(*) AS [value], [t4].[value] AS [value2] 
    FROM (
     SELECT CONVERT(DATE, [t3].[ServerTime]) AS [value] 
     FROM (
      SELECT [t0].[CookieID] 
      FROM [dbo].[Usage] AS [t0] 
      WHERE ([t0].[CookieID] IS NOT NULL) AND ([t0].[ProductID] = @p0) 
      GROUP BY [t0].[CookieID] 
      ) AS [t1] 
     OUTER APPLY (
      SELECT TOP (1) [t2].[ServerTime] 
      FROM [dbo].[Usage] AS [t2] 
      WHERE ((([t1].[CookieID] IS NULL) AND ([t2].[CookieID] IS NULL)) 
      OR (([t1].[CookieID] IS NOT NULL) AND ([t2].[CookieID] IS NOT NULL) 
      AND ([t1].[CookieID] = [t2].[CookieID]))) 
      AND ([t2].[CookieID] IS NOT NULL)   
      AND ([t2].[ProductID] = @p0) 
      ORDER BY [t2].[ServerTime] 
      ) AS [t3] 
     ) AS [t4] 
    GROUP BY [t4].[value] 
    ) AS [t5] 
ORDER BY [t5].[value2] 

由LINQ2SQL表达式生成该查询和从LINQPad萃取。这会产生一个很好的查询计划(据我所知),并在数据库中执行大约10秒钟。但是,如果我用参数替换了两个参数,那就是用'='1fc66e37-6eaf-4032-b374-e7b60fbd25ea'替换两个'= @ p0'部分',我得到了一个不同的估计查询计划,查询现在运行得更长(超过60秒,没有看到它通过)。

为什么执行看似无辜的替换会产生效率更低的查询计划和执行?我用'DBCC FreeProcCache'清除了程序缓存,以确保我没有缓存坏计划,但行为依然存在。

我真正的问题是我可以在10秒的执行时间内生活(至少在很长的一段时间内),但我不能忍受60+秒的执行时间。我的查询会(如上面所暗示的)通过,因此作为

exec sp_executesql N' 
     ... 
     WHERE ([t0].[CookieID] IS NOT NULL) AND ([t0].[ProductID] = @p0) 
     ... 
     AND ([t2].[ProductID] = @p0) 
     ... 
     ',N'@p0 uniqueidentifier',@p0='1FC66E37-6EAF-4032-B374-E7B60FBD25EA' 

产生同样不佳的执行时间(我认为这是双重奇怪,因为这似乎是使用参数化查询的数据库上执行的LINQ2SQL生产。

我不是找提醒在其索引的创建或类似的,我只是想理解为什么查询计划和执行是三个看似相似的查询,以便不同的

编辑:我已经上传了非参数化和执行计划用不同的GUID here

参数化查询以及用于参数化查询(如Heinz建议)执行计划希望它可以帮助你帮我:)

+1

您可以发布您收到的查询计划吗?刚刚运行'SET SHOWPLAN_TEXT ON GO SELECT ...' – Quassnoi 2009-11-03 13:06:44

+0

完成...添加了执行计划的链接... – 2009-11-03 13:42:03

回答

2

我不是在寻找建议创建索引或类似的东西,我只是想了解为什么查询计划和执行是如此不同的三个看似相似的查询。

你似乎有两个指标:

IX_NonCluster_Config (ProductID, ServerTime) 
IX_NonCluster_ProductID_CookieID_With_ServerTime (ProductID, CookieID) INCLUDE (ServerTime) 

第一指标不包括CookieID但下令ServerTime,因此对于选择性ProductID的更有效(即那些你有很多)

第二个索引确实覆盖了所有的列但没有排序,因此更有效的更多选择ProductID的(那些你很少)。

平均而言,基数使得SQL Server期望第二种方法有效,这是您使用参数化查询或明确提供选择性GUID时所使用的方法。

但是,你原来GUID被认为是选择性低,这就是为什么使用第一种方法。

不幸的是,第一种方法需要在CookieID额外的过滤这就是为什么它实际上是低效率的。

+0

啊......也许这也解释了为什么如果我删除where子句的多余部分(检查IS NULL的OR部分是多余的),我会得到“快速”执行CookieID不需要额外的过滤。查询是通过Linq2SQL生成的,我无法真正修改,但我会检查是否可以创建Not-Null列,这似乎删除了附加子句并生成快速查询。 – 2009-11-03 14:37:41

1

我的猜测是,当你走非paramaterized路线,你的guid必须从一个varchar转换为一个UniqueIdentifier,这可能会导致一个索引不被使用,而它将被用于采用非正规路由。

我见过这种情况发生在使用查询的where子句中使用datetime的列的smalldatetime。

3

如果您提供明确的值,SQL Server可以使用此字段的统计信息来制定“更好”的查询计划决策。不幸的是(正如我最近经历的那样),如果统计信息中包含的信息具有误导性,有时SQL Server会做出错误的选择。

如果您想深入研究此问题,我建议您检查如果使用其他GUID会发生什么情况:如果它针对不同的具体GUID使用不同的查询计划,则表示使用统计数据。在这种情况下,您可能需要查看sp_updatestats和相关命令。

编辑:看看DBCC SHOW_STATISTICS:“慢”和“快”GUID可能在直方图中的不同桶中。我有had a similar problem,我通过在SQL中添加一个INDEX table hint来解决这个问题,它“引导”SQL Server寻找“正确的”查询计划。基本上,我已经查看了在“快速”查询期间使用了哪些索引,并将这些索引硬编码到了SQL中。这是远远不是一个最佳或优雅的解决方案,但我还没有找到一个更好的...

+0

试着运行慢,非参数化的另一个GUID,它产生了一个很好的查询计划并按预期执行。你能否详细说明一下我在统计方面需要寻找什么?它是一个需要重建或类似的特定索引吗? – 2009-11-03 13:20:36

+0

我编辑了我的帖子以添加更多详细信息。 – Heinzi 2009-11-03 13:39:32

+0

不可否认,我第一次看到DBBC SHOW_STATISTICS,但我似乎破译了GUID是在单独的桶中(RANGE_ROWS等于316的“慢”和RANGE_ROWS等于0(?)的“快”)。不幸的是我使用Linq2SQL,所以我没有设置查询提示的真正路径。我能否重新计算统计数据? – 2009-11-03 13:57:24

0

很难说,没有看执行计划,但是,如果我要猜测的原因我会说它是一个参数嗅探和糟糕的统计信息的组合 - 在将GUID硬编码到查询中的情况下,查询优化器会尝试优化对该参数值的查询。我相信参数化/准备查询会发生同样的情况(这称为参数嗅探 - 执行计划针对首次执行准备语句时使用的参数进行了优化),但是当您声明时,这绝对不会发生该参数并在查询中使用它。

就像我说的,SQL服务器试图优化执行计划对于那个价值,所以通常你应该看到更好的结果。在这里看来,它基于其决策的信息是不正确的/误导性的,并且当它优化对通用参数值的查询时,您会更好(出于某种原因)。

这主要然而凭空猜测 - 它不可能真的说没有执行 - 如果你可以上传executuion计划地方,那么我肯定会有人能够帮助你的真正原因。

+0

已上传执行计划,请参阅我编辑的文章 – 2009-11-03 14:01:17