由于特定查询使用了错误的执行计划,我遇到了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评论有关
这是两个计划(计划中显示了一些额外的字段,因为它来自真实表):
更新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)
参数:
@p__linq__0 = 1; @p__linq__1 = '2018-01-05 00:00:00.0000000'
@p__linq__0 = 2; @p__linq__1 = '2018-01-05 00:00:00.0000000'
@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})
维护包括所有统计信息,也包括自动生成的统计信息。