100%CPU的执行计划不正确


8

由于特定查询使用了错误的执行计划,我遇到了100%CPU峰值的严重问题。我现在花了数周时间自行解决。

我的资料库

我的样本数据库包含3个简化表。

[数据记录仪]

CREATE TABLE [model].[DataLogger](
    [ID] [bigint] IDENTITY(1,1) NOT NULL,
    [ProjectID] [bigint] NULL,
CONSTRAINT [PK_DataLogger] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

[变频器]

CREATE TABLE [model].[Inverter](
    [ID] [bigint] IDENTITY(1,1) NOT NULL,
    [SerialNumber] [nvarchar](50) NOT NULL,
 CONSTRAINT [PK_Inverter] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY],
 CONSTRAINT [UK_Inverter] UNIQUE NONCLUSTERED 
(
    [DataLoggerID] ASC,
    [SerialNumber] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

ALTER TABLE [model].[Inverter] WITH CHECK
ADD CONSTRAINT [FK_Inverter_DataLogger]
FOREIGN KEY([DataLoggerID])
REFERENCES [model].[DataLogger] ([ID])

[InverterData]

CREATE TABLE [data].[InverterData](
    [InverterID] [bigint] NOT NULL,
    [Timestamp] [datetime] NOT NULL,
    [DayYield] [decimal](18, 2) NULL,
 CONSTRAINT [PK_InverterData] PRIMARY KEY CLUSTERED 
(
    [InverterID] ASC,
    [Timestamp] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF)
)

统计与维护

[InverterData]表包含数百万行(按多个实例的PaaS不同),按月垃圾进行分区。

所有索引器都进行了碎片整理,并且每天/每周都需要根据需要重建/重新组织所有统计信息。

我的查询

该查询是由实体框架生成的,并且也很简单。但是我每分钟要运行1000次,因此性能至关重要。

SELECT
[Extent1].[InverterID] AS [InverterID],
[Extent1].[DayYield] AS [DayYield]
FROM [data].[InverterDayData] AS [Extent1]
INNER JOIN [model].[Inverter] AS [Extent2] ON [Extent1].[InverterID] = [Extent2].[ID]
INNER JOIN [model].[DataLogger] AS [Extent3] ON [Extent2].[DataLoggerID] = [Extent3].[ID]
WHERE ([Extent3].[ProjectID] = @p__linq__0)
AND ([Extent1].[Date] = @p__linq__1) OPTION (MAXDOP 1)

MAXDOP 1提示是一个缓慢的计划相同常另一个问题。

“好”计划

在90%的时间内,所使用的计划快速如闪电,如下所示:

快速计划

问题

白天,好的计划会随机更改为不好的计划和缓慢的计划。

“坏”计划将使用10-60分钟,然后更改回“好”计划。“不良”计划使CPU永久达到100%的利用率。

它是这样的:

慢计划

到目前为止我尝试了什么

我首先想到的Hash Match是那个坏男孩。所以我用新的提示修改了查询。

...Extent1].[Date] = @p__linq__1) OPTION (MAXDOP 1, LOOP JOIN)

LOOP JOIN应强制使用Nested Loop的瞬间Hash Match

结果是90%的计划看起来像以前一样。但是该计划也随机地变成了一个糟糕的计划。

现在,“不良”计划如下所示(表循环顺序已更改):

还慢计划

在“新的不良”计划期间,CPU也会窥视到100%。

解?

我想到要强制执行“好的”计划。但是我不知道这是一个好主意。

计划内建议一个索引,其中包括所有列。但这会使整个表增加一倍,并降低频繁插入的速度。

请帮我!


更新1-与@James评论有关

这是两个计划(计划中显示了一些额外的字段,因为它来自真实表):

好计划

不良方案1(哈希匹配)

不良方案2(嵌套循环)

更新2-与@David Fowler回答有关

不好的计划是对随机参数值进行干预。因此,通常情况下,我@p__linq__1 ='2016-11-26 00:00:00.0000000' @p__linq__0 =20825的漏洞日和较差的计划具有相同的价值。

我知道存储过程中的参数嗅探问题,以及如何避免在SP中使用它们。您是否对我有提示,如何避免我的查询出现此问题?

创建推荐的索引将包括所有列。这将使整个表增加一倍,并减慢频繁插入的速度。建立一个简单地克隆表的索引并不“正确”。我的意思是将这个大表的数据大小加倍。

更新3-有关@David Fowler的评论

它也行不通,我认为行不通。为了更好地理解,我将向您解释如何调用查询。

假设我在[DataLogger]表中有3个实体。一天来回,我一次又一次地调用相同的3个查询:

基本查询: ...WHERE ([Extent3].[ProjectID] = @p__linq__0) AND ([Extent1].[Date] = @p__linq__1)

参数:

  1. @p__linq__0 = 1; @p__linq__1 = '2018-01-05 00:00:00.0000000'
  2. @p__linq__0 = 2; @p__linq__1 = '2018-01-05 00:00:00.0000000'
  3. @p__linq__0 = 3; @p__linq__1 = '2018-01-05 00:00:00.0000000'

参数@p__linq__1始终是同一日期。但是它会在查询之前随机选择错误的计划,而该查询以前会被一个好的计划所取代。具有相同的参数!

更新4-与@Nic评论有关

维护工作每晚进行,看起来像这样。

指数

如果索引的碎片程度超过5%,则会对其进行重组...

ALTER INDEX [{index}] ON [{table}] REORGANIZE

如果索引碎片超过30%,则会对其进行重建...

ALTER INDEX [{index}] ON [{table}] REBUILD WITH (ONLINE=ON, MAXDOP=1)

如果对索引进行了分区,它将证明碎片并且每个分区都会改变...

ALTER INDEX [{index}] ON [{table}] REBUILD PARTITION = {partitionNr} WITH (ONLINE=ON, MAXDOP=1)

统计

如果modification_counter高于0,则会更新所有统计信息。

UPDATE STATISTICS [{schema}].[{object}] ([{stats}]) WITH FULLSCAN

或在分区上。

UPDATE STATISTICS [{schema}].[{object}] ([{stats}]) WITH RESAMPLE ON PARTITIONS({partitionNr})

维护包括所有统计信息,也包括自动生成的统计信息。

例


您没有提及此信息,但是您最近是否更新了其中任何表格的统计信息?
Nic

Thx @Nic我在问题上添加了更新。请在此处查看详细信息。
Steffen Mangold

Answers:


3

看一下计划,好的计划和坏的计划之间有一些区别。首先要注意的是,好的计划对InverterDayData执行搜索,因为这两个坏计划都执行扫描。为什么会这样,如果检查估计的行,您会看到好计划期望1行,而坏计划期望6661,大约7000行。

现在看一下编译后的参数值,

好计划 @ p__linq__1 ='2016-11-26 00:00:00.0000000'@ p__linq__0 = 20825

错误计划 @ p__linq__1 ='2018-01-03 00:00:00.0000000'@ p__linq__0 = 20686

因此,在我看来,这就像一个参数嗅探问题,当查询性能不佳时,您要向该查询传递哪些参数值?

在InverterDayData的不良计划中有一个索引建议看起来很明智,我尝试在其中运行它,看看是否有帮助。它可能允许SQL对表执行扫描。


感谢您的回答!我已经为您修改了这个问题(最后)。
Steffen Mangold

我没有意识到建议的索引可以覆盖整个表,这不是一个好主意。您可以尝试在日期列上建立索引。
David Fowler '18

使用OPTIMIZE FOR提示将迫使计划以良好的值OPTION进行编译(OPTIMIZE FOR(@ p__linq__1 ='2016-11-26 00:00:00.0000000',@ p__linq__0 20825)),但请谨慎操作并进行测试它可以确保不会引起任何意外的头部疼痛
David Fowler

嘿@david我发布了更新。
斯特芬·曼高尔德

好的,这是一个参数嗅探问题!由于分区表中的行数不同,因此选择了索引SEEK或SCAN。我用...OPTION (OPTIMIZE FOR UNKNOWN)提示修复了它。
Steffen Mangold
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.