SQL Server查询计划的差异

时间:2009-11-03 12:53:56

标签: sql-server sql-execution-plan parameterized

当从参数化查询更改为非参数化查询时,我无法理解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秒的执行时间。我的查询将(如上所述)由Linq2SQL生成,因此它在数据库上执行为

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'

产生同样糟糕的执行时间(我认为这很奇怪,因为这似乎是使用参数化查询。

我不是在寻找关于要创建哪些索引等的建议,我只是想了解为什么查询计划和执行在三个看似相似的查询上如此不同。

编辑:我已经上传了非参数化和参数化查询的执行计划,以及参数化查询的执行计划(由Heinz建议)和不同的GUID here

希望它可以帮助你帮助我:)。

4 个答案:

答案 0 :(得分:3)

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

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

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

答案 1 :(得分:2)

  

我不是在寻找关于要创建哪些索引等的建议,我只是想了解为什么查询计划和执行在三个看似相似的查询上如此不同。

您似乎有两个索引:

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

第一个索引不包含CookieID,但是在ServerTime上排序,因此对 less 选择性ProductID更有效(即那些你有很多)

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

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

但是,您的原始GUID被认为选择性较低,这就是使用第一种方法的原因。

不幸的是,第一种方法需要对CookieID进行额外的过滤,这就是为什么事实上效率较低的原因。

答案 2 :(得分:1)

我的猜测是,当您采用非参数化路线时,您的guid必须从varchar转换为UniqueIdentifier,这可能导致索引不被使用,而它将被用于采用paramatarized路线。

我见过这种情况发生在使用where子句中的smalldatetime对使用日期时间的列的查询。

答案 3 :(得分:0)

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

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

然而,这主要是猜测 - 如果没有执行就不可能真正说出来 - 如果您可以在某处上传执行计划,那么我相信有人能够帮助您解决真正的原因。