У меня огромная проблема со 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})
В обслуживание входит вся статистика, а также автоматически сгенерированная.
источник
Ответы:
Посмотрите на планы, есть несколько различий между хорошим и плохим. Первое, на что следует обратить внимание, это то, что хороший план выполняет поиск по 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 выполнить сканирование таблицы.
источник
...OPTION (OPTIMIZE FOR UNKNOWN)
подсказкой.