【问题标题】:Parameter Sniffing causing slowdown for text-base query, how to remove execution plan?参数嗅探导致基于文本的查询变慢,如何删除执行计划?
【发布时间】:2014-04-21 22:35:15
【问题描述】:

我有一个 sql 查询,其确切代码是在 C# 中生成的,并通过 ADO.Net 作为基于文本的 SqlCommand 传递。

查询看起来像这样:

SELECT TOP (@n)
        a.ID,
        a.Event_Type_ID as EventType,
        a.Date_Created,
        a.Meta_Data
FROM net.Activity a

LEFT JOIN net.vu_Network_Activity na WITH (NOEXPAND)
ON na.Member_ID = @memberId AND na.Activity_ID = a.ID

LEFT JOIN net.Member_Activity_Xref ma
ON ma.Member_ID = @memberId AND ma.Activity_ID = a.ID

WHERE 
    a.ID < @LatestId 
AND ( 
        Event_Type_ID IN(1,2,3))
        OR 
        (
            (na.Activity_ID IS NOT NULL OR ma.Activity_ID IS NOT NULL) 
            AND 
            Event_Type_ID IN(4,5,6)
        ) 
    )
ORDER BY a.ID DESC

此查询已经运行良好一段时间了。它利用了我们在这些表上的一些索引。

无论如何,这个查询突然开始运行非常缓慢,但在 SSMS 中几乎是瞬间运行。

最终,在阅读了一些资源后,我能够验证我们得到的减速是由于参数嗅探不佳造成的。

通过将所有参数复制到局部变量,我能够成功地减少问题。问题是,这对我来说感觉有点不对劲。

我假设发生的事情是其中一个表的统计信息被更新,然后运气不好,第一次重新编译这个查询时,它被调用的参数值导致执行计划不同?

我能够在活动监视器中跟踪查询,导致查询在约 13 秒内运行的执行计划是:

在 SSMS 中运行会产生以下执行计划(并且只需要大约 100 毫秒):

那么问题是什么?

我想我的问题是:如何解决这个问题,而不将参数复制到局部变量,could lead to a large number of cached execution plans?

引用自linked comment / Jes Borland:

您可以在存储过程中使用局部变量来“避免”参数嗅探。但是,请理解,这可能会导致许多计划存储在缓存中。这可能有其自身的性能影响。没有一个万能的解决方案!

我的想法是,如果我有某种方法可以手动从临时数据库中删除当前的执行计划,那可能就足够了......但我在网上找到的所有内容都只向我展示了如何为实际命名的存储过程。

这是一个来自C#的基于文本的SqlCommand,所以我不知道如何找到缓存的执行计划,以及嗅探到的参数值,并将其删除?

注意:“只创建一个适当的存储过程”这个有点明显的解决方案很难做到,因为这个查询可以通过多种不同的方式生成......并且需要进行一些不愉快的重构。

【问题讨论】:

  • net.activity里面有member_id吗?
  • @TonyHopkinson 不,它没有。为什么?
  • 本可以将 @member_id 上的两个左连接换成 members.member_id 少一个参数来嗅探,尽管我怀疑 Top(@n) 是给你带来了最严重的问题,再看一遍。跨度>

标签: sql sql-server sql-server-2008 tsql stored-procedures


【解决方案1】:

如果你想从缓存中删除一个特定的计划,那么它实际上是一个两步过程:首先获取该特定计划的计划句柄;然后使用 DBCC FREEPROCCACHE 从缓存中删除该计划。

要获取计划句柄,您需要查看执行计划缓存。下面的 T-SQL 是一个示例,说明如何搜索计划并获取句柄(您可能需要稍微使用 filter 子句来磨练您的特定计划):

SELECT top (10)
    qs.last_execution_time,
    qs.creation_time,
    cp.objtype, 
   SUBSTRING(qt.[text], qs.statement_start_offset/2, ( 
       CASE  
           WHEN qs.statement_end_offset = -1 
                THEN LEN(CONVERT(NVARCHAR(MAX), qt.[text])) * 2  
           ELSE qs.statement_end_offset  
       END - qs.statement_start_offset)/2  + 1
   ) AS query_text, 
   qt.text as full_query_text, 
   tp.query_plan,
   qs.sql_handle,
   qs.plan_handle
FROM  
   sys.dm_exec_query_stats qs 
   LEFT JOIN sys.dm_exec_cached_plans cp ON cp.plan_handle=qs.plan_handle
   CROSS APPLY sys.dm_exec_sql_text (qs.[sql_handle]) AS qt 
   OUTER APPLY sys.dm_exec_query_plan(qs.plan_handle) tp 
WHERE qt.text like '%vu_Network_Activity%'

获得计划句柄后,调用 DBCC FREEPROCCACHE,如下所示:

DBCC FREEPROCCACHE(<plan_handle>)

【讨论】:

  • 感谢您的及时答复!我正在试一试,会回来报告的。
  • 这最终像宣传的那样工作。注意:我删除了查询中的左连接+外部应用以查找计划句柄,因为查询太慢了。
【解决方案2】:

delete/invalidate a query plan有多种方式:

DBCC FREEPROCCACHE(plan_handle)

或

EXEC sp_recompile 'net.Activity'

或

在查询末尾添加OPTION (RECOMPILE) 查询提示

或

使用optimize for ad hoc workloads 服务器设置

或 更新统计数据

【讨论】:

    【解决方案3】:

    如果你有来自糟糕供应商的糟糕产品,处理参数嗅探的最佳方法是使用 EXEC sp_create_plan_guide/ 创建你自己的计划

    【讨论】:

      猜你喜欢
      • 2019-06-19
      • 1970-01-01
      • 2010-11-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-25
      • 2017-03-27
      相关资源
      最近更新 更多