Ho un grosso problema con picchi di CPU al 100% a causa di un piano di esecuzione errato utilizzato da una query specifica. Passo settimane a risolverlo da solo.
Il mio database
Il mio DB di esempio contiene 3 tabelle semplificate.
[Datalogger]
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]
[Inverter]
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)
)
Statistica e manutenzione
La [InverterData]tabella contiene più milioni di righe (differisce in più istanze PaaS) partizionate in giunche mensili.
Tutti gli indicizzatori vengono deframmentati e tutte le statistiche vengono ricostruite / riorganizzate secondo necessità in un turno giornaliero / settimanale.
La mia domanda
La query è generata da Entity Framework e anche semplice. Ma corro 1.000 volte al minuto e le prestazioni sono essenziali.
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)
Il MAXDOP 1suggerimento è per un altro problema con un piano parallelo parallelo.
Il "buon" piano
Nel 90% dei casi il piano utilizzato è velocissimo e si presenta così:
Il problema
Nel corso della giornata il buon piano è cambiato casualmente in un piano cattivo e lento.
Il piano "cattivo" viene utilizzato per 10-60 minuti e quindi ripristinato al piano "buono". Il piano "cattivo" porta la CPU al 100% permanente.
Ecco come appare:
Quello che provo finora
Il mio primo pensiero è stato Hash Matchil cattivo ragazzo. Quindi ho modificato la query con un nuovo suggerimento.
...Extent1].[Date] = @p__linq__1) OPTION (MAXDOP 1, LOOP JOIN)
L' LOOP JOINdovrebbe costringere a usare Nested Loopistante di Hash Match.
Il risultato è che il piano del 90% appare come prima. Ma il piano è cambiato anche casualmente in uno cattivo.
Il piano "cattivo" ora assomiglia a questo (ordine del loop di tabella modificato):
La CPU fa anche capolino al 100% durante il piano "nuovo cattivo".
Soluzione?
Mi viene in mente di forzare il "buon" piano. Ma non so se questa è una buona idea.
All'interno del piano è consigliato un indice che includesse tutte le colonne. Ma questo raddoppierà il tavolo completo e rallenterà gli inserst che sono molto frequenti.
Mi aiuti per favore!
Aggiornamento 1 - correlato al commento @James
Ecco entrambi i piani (alcuni campi extra mostrati nel piano perché provengono dalla tabella reale):
Aggiornamento 2 - relativo alla risposta di @David Fowler
Il cattivo piano sta dando il via al valore di parametro casuale. Così normalmente @p__linq__1 ='2016-11-26 00:00:00.0000000' @p__linq__0 =20825il giorno del buco e il piano cattivo che arriva sullo stesso valore.
Conosco il problema dello sniffing dei parametri dalle procedure memorizzate e come evitarli all'interno di SP. Hai un suggerimento per me come evitare questo problema per la mia query?
La creazione dell'indice raccomandato includerà tutte le colonne. Questo raddoppierà la tabella completa e rallenterà gli inserst, che sono molto frequenti. Ciò non "sembra" giusto per costruire un indice che cloni semplicemente la tabella. Inoltre intendo raddoppiare la dimensione dei dati di questa grande tabella.
Aggiornamento 3 - correlato al commento di @David Fowler
Inoltre non ha funzionato e penso che non potrebbe. Per una migliore comprensione ti spiegherò come viene chiamata la query.
Supponiamo che io abbia 3 entità nella [DataLogger]tabella. Nel corso della giornata chiamo ripetutamente le stesse 3 domande in un viaggio di andata e ritorno:
Query di base:
...WHERE ([Extent3].[ProjectID] = @p__linq__0) AND ([Extent1].[Date] = @p__linq__1)
Parametro:
@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'
Il parametro @p__linq__1è sempre la stessa data. Ma sceglie il piano errato in modo casuale su una query che viene eseguita migliaia di volte con un buon piano prima. Con lo stesso parametro!
Aggiornamento 4 - relativo al commento @Nic
La manutenzione viene eseguita ogni notte e si presenta così.
Indice
Se un indice viene frammentato più del 5%, viene riorganizzato ...
ALTER INDEX [{index}] ON [{table}] REORGANIZE
Se un indice viene frammentato più del 30%, viene ricostruito ...
ALTER INDEX [{index}] ON [{table}] REBUILD WITH (ONLINE=ON, MAXDOP=1)
Se l'Indice è partizionato verrà sottoposto a prova di frammentazione e modificato per partizione ...
ALTER INDEX [{index}] ON [{table}] REBUILD PARTITION = {partitionNr} WITH (ONLINE=ON, MAXDOP=1)
statistica
Tutte le statistiche verranno aggiornate se modification_counterè superiore a 0 ...
UPDATE STATISTICS [{schema}].[{object}] ([{stats}]) WITH FULLSCAN
o su partizionato ..
UPDATE STATISTICS [{schema}].[{object}] ([{stats}]) WITH RESAMPLE ON PARTITIONS({partitionNr})
La manutenzione include tutte le statistiche, anche quella generata automaticamente.



