6

当从参数化查询更改为非参数化查询时,我无法理解 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'

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

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

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

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

4

4 回答 4

3

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

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

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

于 2009-11-03T13:03:24.157 回答
2

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

您似乎有两个索引:

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

第一个索引不涵盖CookieID但已排序ServerTime,因此对于选择性较低ProductID的 's(即你有很多的那些)更有效

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

平均而言,您ProductID的基数SQL Server期望第二种方法是有效的,当您使用参数化查询或显式提供 select 时,它就是使用这种方法GUID

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

不幸的是,第一种方法需要额外的过滤,CookieID这就是它实际上效率较低的原因。

于 2009-11-03T14:11:02.873 回答
1

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

我已经看到这种情况发生在使用 where 子句中具有 smalldatetime 的查询对使用日期时间的列。

于 2009-11-03T12:58:19.353 回答
0

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

就像我说的,SQL Server 尝试优化该值的执行计划因此通常您应该会看到更好的结果。在这里,它所依据的信息似乎是不正确/具有误导性的,当它优化通用参数值的查询时,你会更好(出于某种原因)。

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

于 2009-11-03T13:25:24.283 回答