为什么SQL Server突然决定使用如此糟糕的执行计划?

时间:2015-01-09 00:13:13

标签: sql sql-server performance

背景

我们最近遇到了sql server在我们的一个较大的表(大约175,000,000行)上使用的查询计划的问题。该表的列和索引结构未发生5年以上的变化。

表和索引如下所示:

create table responses (
    response_uuid uniqueidentifier not null,
    session_uuid uniqueidentifier not null,
    create_datetime datetime not null,
    create_user_uuid uniqueidentifier not null,
    update_datetime datetime not null,
    update_user_uuid uniqueidentifier not null,
    question_id int not null,
    response_data varchar(4096) null,
    question_type_id varchar(3) not null,
    question_length tinyint null,
    constraint pk_responses primary key clustered (response_uuid),
    constraint idx_responses__session_uuid__question_id unique nonclustered (session_uuid asc, question_id asc) with (fillfactor=80),
    constraint fk_responses_sessions__session_uuid foreign key(session_uuid) references dbo.sessions (session_uuid),
    constraint fk_responses_users__create_user_uuid foreign key(create_user_uuid) references dbo.users (user_uuid),
    constraint fk_responses_users__update_user_uuid foreign key(update_user_uuid) references dbo.users (user_uuid)
)

create nonclustered index idx_responses__session_uuid_fk on responses(session_uuid) with (fillfactor=80)

表现不佳的查询(约2.5分钟而不是正常的<1秒表现)看起来像这样:

SELECT 
[Extent1].[response_uuid] AS [response_uuid], 
[Extent1].[session_uuid] AS [session_uuid], 
[Extent1].[create_datetime] AS [create_datetime], 
[Extent1].[create_user_uuid] AS [create_user_uuid], 
[Extent1].[update_datetime] AS [update_datetime], 
[Extent1].[update_user_uuid] AS [update_user_uuid], 
[Extent1].[question_id] AS [question_id], 
[Extent1].[response_data] AS [response_data], 
[Extent1].[question_type_id] AS [question_type_id], 
[Extent1].[question_length] AS [question_length]
FROM [dbo].[responses] AS [Extent1]
WHERE [Extent1].[session_uuid] = @f6_p__linq__0;

(查询由实体框架生成并使用sp_executesql执行)

表现不佳期间的执行计划如下:

execution plan

运行上述查询的数据的某些背景永远不会返回超过400行。换句话说,对session_uuid进行过滤实际上会减少结果集。

计划维护的一些背景知识 - 计划作业每周运行一次,以重建数据库的统计数据并重建表的索引。该作业运行如下所示的脚本:

alter index all on responses rebuild with (fillfactor=80)

性能问题的解决方法是在此表上运行重建索引脚本(上面)。

其他可能相关的信息花絮......自上次索引重建以来,数据分发根本没有变化。查询中没有联接。我们是SAAS商店,我们拥有50到100个具有完全相同模式的实时生产数据库,一些数据库更多,一些数据库更少,所有执行相同查询的数据都分布在几个sql服务器上。

问题:

可能发生什么会让sql server在这个特定的数据库中开始使用这个可怕的执行计划?

请记住,只需重建表上的索引即可解决问题。

也许更好的问题是&#34; sql server停止使用索引的情况是什么?&#34;

另一种看待它的方法是&#34;为什么优化器不会使用几天前重建的索引,然后在我们注意到错误的查询计划后执行索引的紧急重建后再次开始使用它?&#34;

3 个答案:

答案 0 :(得分:8)

评论太长了。

原因很简单:优化器会根据最佳计划改变主意。这可能是由于数据分布的细微变化(或其他原因,例如join密钥中的类型不兼容)。我希望有一个工具不仅可以为查询提供执行计划,还可以显示与另一个执行计划的接近程度的阈值。或者一种工具,可以让您存储执行计划,并在同一查询开始使用其他计划时发出警报。

我不止一次地问自己这个完全相同的问题。你有一个系统每晚运行几个月。它使用非常复杂的查询处理大量数据。然后,有一天,你早上进来,工作通常在晚上11点结束。还在运行Arrrggg!

我们提出的解决方案是对失败的连接使用显式join提示。 (option (merge join, hash join))。我们还开始为所有复杂查询保存执行计划,因此我们可以比较从一个晚上到下一个的变化。最后,这比实际利益更具学术兴趣 - 当计划改变时,我们已经遭受了糟糕的执行计划。

答案 1 :(得分:1)

这是我最讨厌的SQL问题-由于这个问题,我已经经历了多个失败-一旦已经运行了几个月的查询从〜250ms超过了超时阈值,导致制造系统崩溃当然是凌晨3点。花了一段时间来隔离查询并将其粘贴到SSMS中,然后开始将其分解成碎片-但是我所做的一切都只是“有效”。最后,我只是在查询中添加了短语“ AND 1 = 1”,这使事情在几周内再次起作用-最终的补丁是“盲目”优化程序-基本上将所有传递的参数复制到本地参数中。如果查询工作正常,看来它将继续有效。

对我来说,MS的一个相对简单的解决方法是:如果已经对该查询进行了分析并且上次运行良好,并且相关统计信息没有显着变化(例如,提出了表中各种变化的一些因素)或新索引等),然后“优化程序”决定为新的执行计划增添趣味,如果新的和改进的计划所花费的时间超过旧计划的X倍,我会中止然后再次切换回去。我知道表是否从100行增加到100,000,000行,或者是否删除了键索引,但是对于稳定的生产环境而言,查询的持续时间跳到慢100倍至1000倍之间,检测这一点并不难,标记该计划,然后返回上一个。

答案 2 :(得分:0)

较新的SQL Server版本具有一项称为“查询存储”的强大新功能,您可以在其中分析最近的查询性能。

如果看到查询有时使用“快速”计划而有时使用“缓慢”的查询,则可以强制执行快速计划。查看屏幕截图。 “黄色圆圈”计划是快速的计划,但“蓝色正方形”计划则不是(在“持续时间”图表上更高)

query store