13

我读过很多关于参数嗅探的文章,但不清楚这是好是坏。谁能用一个简单的例子来解释这一点。

有没有办法自动检测分配给特定语句的错误计划?

提前致谢。

4

2 回答 2

20

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

参数嗅探是关于查询优化器使用提供的参数的值来找出可能的最佳查询计划。众多选择之一且很容易理解的是,是否应该扫描整个表以获取值,或者使用索引查找是否会更快。如果您的参数中的值具有高度选择性,则优化器可能会使用搜索构建查询计划,如果不是,则查询将扫描您的表。

然后查询计划被缓存并重用于具有不同值的连续查询。参数嗅探的坏处是缓存计划不是这些值之一的最佳选择。

样本数据:

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 上具有非聚集索引。有一行 value 1,其余的有 value 2

示例查询:

select *
from T 
where Value = @Value;

查询优化器在这里的选择是执行聚集索引扫描并检查每一行的 where 子句,或者使用索引查找查找匹配的行,然后执行键查找以从请求的列中获取值列列表。

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

在此处输入图像描述

当嗅探到的值是2这样的:

在此处输入图像描述

在这种情况下,参数嗅探的坏部分发生在构建查询计划时嗅探 a1但稍后使用 的值执行2

在此处输入图像描述

可以看到 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
;
于 2013-12-20T09:05:18.983 回答
0

是的,有时它是好是坏。

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

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

于 2015-08-21T19:51:45.540 回答