Eu tenho um problema enorme com picos de CPU de 100% devido a um plano de execução incorreto usado por uma consulta específica. Agora passo semanas resolvendo sozinho.
Meu banco de dados
Meu banco de dados de amostra contém 3 tabelas simplificadas.
[Data logger]
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]
[Inversor]
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)
)
Estatísticas e Manutenção
A [InverterData]
tabela contém vários milhões de linhas (difere em várias instâncias PaaS) particionadas em lixo eletrônico mensal.
Todos os indexadores são desfragmentados e todas as estatísticas são reconstruídas / reorganizadas conforme necessário em um turno diário / semanal.
Minha consulta
A consulta é gerada pelo Entity Framework e também simples. Mas eu corro 1.000 vezes por minuto e o desempenho é essencial.
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)
A MAXDOP 1
dica é para outro problema com um plano paralelo lento.
O plano "bom"
Nos 90% do tempo, o plano usado é extremamente rápido e fica assim:
O problema
Ao longo do dia, o plano bom mudou aleatoriamente para um plano ruim e lento.
O plano "ruim" é usado por 10 a 60 minutos e depois volta ao plano "bom". O plano "ruim" aumenta a CPU até 100% permanente.
Isto é o que parece:
O que eu tento até agora
Meu primeiro pensamento foi o Hash Match
é o menino mau. Então, modifiquei a consulta com uma nova dica.
...Extent1].[Date] = @p__linq__1) OPTION (MAXDOP 1, LOOP JOIN)
O LOOP JOIN
deve forçar a usar Nested Loop
instante de Hash Match
.
O resultado é que o plano de 90% se parece com antes. Mas o plano também mudou aleatoriamente para ruim.
O plano "ruim" agora se parece com isso (a ordem do loop da tabela foi alterada):
A CPU também espreita para 100% durante o plano "novo ruim".
Solução?
Penso em forçar o plano "bom". Mas não sei se é uma boa ideia.
Dentro do plano, é recomendado um índice que inclua todas as colunas. Mas isso dobrará a tabela completa e diminuirá o conhecimento, que é altamente frequente.
Por favor me ajude!
Atualização 1 - relacionada ao comentário do @James
Aqui estão os dois planos (alguns campos extras mostrados no plano porque são da tabela real):
Plano incorreto 2 (loop aninhado)
Atualização 2 - relacionada a @David Fowler replyere
O plano ruim está entrando no valor do parâmetro aleatório. Então, normalmente eu @p__linq__1 ='2016-11-26 00:00:00.0000000' @p__linq__0 =20825
no dia do buraco e que o plano ruim vem com o mesmo valor.
Conheço o problema de detecção de parâmetros nos procedimentos armazenados e como evitá-los dentro do SP. Você tem uma dica para mim como evitar esse problema na minha consulta?
A criação do índice recomendado incluirá todas as colunas. Isso dobrará a tabela completa e diminuirá o desempenho, que são altamente frequentes. Isso não "parece" certo para criar um índice que simplesmente clone a tabela. Também quero dobrar o tamanho dos dados dessa grande tabela.
Atualização 3 - relacionada ao comentário de @David Fowler
Também não funcionou e acho que não. Para uma melhor compreensão, explicarei como a consulta é chamada.
Vamos supor que eu tenha 3 entidades na [DataLogger]
tabela. Durante o dia, chamo as mesmas 3 consultas em uma viagem de ida e volta:
Consulta base:
...WHERE ([Extent3].[ProjectID] = @p__linq__0) AND ([Extent1].[Date] = @p__linq__1)
Parâmetro:
@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'
O parâmetro @p__linq__1
é sempre a mesma data. Mas escolhe aleatoriamente o plano ruim em uma consulta que executa milhares de vezes com um bom plano antes. Com o mesmo parâmetro!
Atualização 4 - relacionada ao comentário @Nic
A manutenção é executada todas as noites e fica assim.
Índice
Se um índice é fragmentado mais de 5%, é reorganizado ...
ALTER INDEX [{index}] ON [{table}] REORGANIZE
Se um índice é fragmentado mais de 30%, é reconstruído ...
ALTER INDEX [{index}] ON [{table}] REBUILD WITH (ONLINE=ON, MAXDOP=1)
Se o Índice for particionado, ele será testado quanto à fragmentação e alterado por partição ...
ALTER INDEX [{index}] ON [{table}] REBUILD PARTITION = {partitionNr} WITH (ONLINE=ON, MAXDOP=1)
Estatisticas
Todas as estatísticas serão atualizadas se modification_counter
for maior que 0 ...
UPDATE STATISTICS [{schema}].[{object}] ([{stats}]) WITH FULLSCAN
ou em particionado ..
UPDATE STATISTICS [{schema}].[{object}] ([{stats}]) WITH RESAMPLE ON PARTITIONS({partitionNr})
A manutenção inclui todas as estatísticas, também a gerada automaticamente.
fonte
Respostas:
Veja os planos, existem algumas diferenças entre as boas e as ruins. A primeira coisa a notar é que o plano bom realiza uma busca no InverterDayData, onde os dois planos ruins executam uma verificação. Por que é isso, se você verificar as linhas estimadas, verá que o plano bom está esperando uma linha, enquanto os planos ruins estão esperando 6661 e cerca de 7000 linhas.
Agora, veja os valores dos parâmetros compilados,
Bom plano @ p__linq__1 = '2016-11-26 00: 00: 00.0000000' @ p__linq__0 = 20825
O que você precisa saber para começar o dia bem-informado
então, para mim, parece um problema de detecção de parâmetros, que valores de parâmetro você está passando para essa consulta quando está com um desempenho ruim?
Há uma recomendação de índice nos planos ruins no InverterDayData que parece sensata, eu tentaria executá-lo e ver se isso ajuda você. Pode permitir que o SQL execute uma varredura na tabela.
fonte
...OPTION (OPTIMIZE FOR UNKNOWN)
dica.