SQL Server - 参数嗅探

时间:2013-12-20 08:36:35

标签: sql-server parameter-sniffing

我读过很多关于参数嗅探的文章,但目前尚不清楚这是好还是坏。任何人都可以通过一个简单的例子解释这一点。

有没有办法自动检测错误的计划是否已分配给特定的声明?

提前致谢。

2 个答案:

答案 0 :(得分:20)

这很好,但有时可能会很糟糕。

参数嗅探是关于查询优化器使用提供的参数的值来确定可能的最佳查询计划。许多选择中的一个和非常容易理解的选择之一是,应该扫描整个表以获取值,还是使用索引搜索更快。如果参数中的值具有高度选择性,优化器可能会构建一个包含seek的查询计划,如果不是,查询将对您的表进行扫描。

然后缓存查询计划并重复使用具有不同值的连续查询。参数嗅探的不好之处在于缓存计划不是其中一个值的最佳选择。

示例数据:

create table T
(
  ID int identity primary key,
  Value int not null,
  AnotherValue int null
);

create index IX_T_Value on T(Value);

insert into T(Value) values(1);

insert into T(Value)
select 2
from sys.all_objects;

T是一个包含几千行且在Value上具有非聚集索引的表。有一行的值为1,其余的值为2

示例查询:

select *
from T 
where Value = @Value;

查询优化器在此处的选择是执行聚簇索引扫描并针对每一行检查where子句,还是使用索引查找来查找匹配的行,然后执行密钥查找以从列中获取值列列表中要求。

当嗅探值为1时,查询计划将如下所示:

enter image description here

当嗅探值为2时,它将如下所示:

enter image description here

在这种情况下参数嗅探的不良部分发生在构建查询计划时,嗅探1但稍后执行2的值。

enter image description here

您可以看到Key Lookup执行了2352次。扫描显然是更好的选择。

总结一下,我会说参数嗅探是一件好事,你应该尽量通过使用查询参数来实现。有时它可能会出错,在这种情况下,很可能是由于数据偏差导致您的统计信息混乱。

<强>更新

这是针对几个dmv的查询,您可以使用它来查找系统上最昂贵的查询。更改为order by子句,以便根据您的要求使用不同的标准。我认为TotalDuration是一个很好的起点。

set transaction isolation level read uncommitted;

select top(10)
  PlanCreated       = qs.creation_time,
  ObjectName        = object_name(st.objectid),
  QueryPlan         = cast(qp.query_plan as xml),
  QueryText         = substring(st.text, 1 + (qs.statement_start_offset / 2), 1 + ((isnull(nullif(qs.statement_end_offset, -1), datalength(st.text)) - qs.statement_start_offset) / 2)),
  ExecutionCount    = qs.execution_count,
  TotalRW           = qs.total_logical_reads + qs.total_logical_writes,
  AvgRW             = (qs.total_logical_reads + qs.total_logical_writes) / qs.execution_count,
  TotalDurationMS   = qs.total_elapsed_time / 1000,
  AvgDurationMS     = qs.total_elapsed_time / qs.execution_count / 1000,
  TotalCPUMS        = qs.total_worker_time / 1000,
  AvgCPUMS          = qs.total_worker_time / qs.execution_count / 1000,
  TotalCLRMS        = qs.total_clr_time / 1000,
  AvgCLRMS          = qs.total_clr_time / qs.execution_count / 1000,
  TotalRows         = qs.total_rows,
  AvgRows           = qs.total_rows / qs.execution_count
from sys.dm_exec_query_stats as qs
  cross apply sys.dm_exec_sql_text(qs.sql_handle) as st
  cross apply sys.dm_exec_text_query_plan(qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) as qp
--order by ExecutionCount desc
--order by TotalRW desc
order by TotalDurationMS desc
--order by AvgDurationMS desc
;

答案 1 :(得分:0)

是的,有时它是好还是坏。

许多时间查询优化器选择旧的查询计划来执行,因为它将此计划存储到缓存中以用于频繁运行的查询。 现在当旧的查询计划具有表扫描参数时发生了什么,该参数在增加记录后需要更改索引扫描。

我发现在我的情况下,查询优化器使用旧的查询计划而不是创建新的查询计划。查询优化器使用查询缓存中的旧查询计划。 我在参数嗅探方面创造了非常有趣的帖子。请访问此网址: http://www.dbrnd.com/2015/05/sql-server-parameter-sniffing/