У меня огромная проблема со 100% -ными скачками ЦП из-за неверного плана выполнения, используемого конкретным запросом. Я провожу недели, теперь решаю сам.
Моя база данных
Моя примерная БД содержит 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) секционированных в ежемесячных джонки.
Все индексаторы дефрагментированы и все статистические данные перестраиваются / реорганизуются по мере необходимости на ежедневном / еженедельном ходу.
Мой запрос
Запрос генерируется Entity Framework, а также прост. Но я бегаю 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 минут, а затем возвращается к «хорошему» плану. «Плохой» план поднимает процессор до 100%.
Вот как это выглядит:
Что я пытаюсь до сих пор
Моей первой мыслью было, что Hash Match
это плохой мальчик. Поэтому я изменил запрос с новой подсказкой.
...Extent1].[Date] = @p__linq__1) OPTION (MAXDOP 1, LOOP JOIN)
LOOP JOIN
Должны заставить использовать Nested Loop
мгновение Hash Match
.
В результате план на 90% выглядит как раньше. Но план также случайно изменился на плохой.
«Плохой» план теперь выглядит следующим образом (изменен порядок цикла таблицы):
Процессор также подсматривает до 100% во время «нового плохого» плана.
Решение?
Мне приходит в голову форсировать «хороший» план. Но я не знаю, хорошая ли это идея.
Внутри плана рекомендуется индекс, включающий все столбцы. Но это удвоит всю таблицу и замедлит вставку, что очень часто.
Пожалуйста, помогите мне!
Обновление 1 - связано с комментарием @James
Вот оба плана (некоторые дополнительные поля показаны в плане, потому что это из реальной таблицы):
Обновление 2 - связано с ответом @David Fowler
Плохой план включает в себя случайное значение параметра. Так что обычно я дырявый @p__linq__1 ='2016-11-26 00:00:00.0000000' @p__linq__0 =20825
день и плохой план, приходящий на одно и то же значение.
Я знаю проблему перехвата параметров из хранимых процедур и как избежать их внутри SP. У вас есть подсказка для меня, как избежать этой проблемы для моего запроса?
Создание рекомендуемого индекса будет включать все столбцы. Это удвоит всю таблицу и замедлит вставку, что очень часто. Это не "чувствует" право строить индекс, который просто клонирует таблицу. Также я имею в виду удвоить размер данных этой большой таблицы.
Обновление 3 - связано с комментарием Дэвида Фаулера
Это также не сработало, и я думаю, что не смогло. Для лучшего понимания я объясню вам, как называется запрос.
Предположим, у меня есть 3 сущности в [DataLogger]
таблице. В течение дня я снова и снова звоню на одни и те же 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})
В обслуживание входит вся статистика, а также автоматически сгенерированная.