Come ho migliorato una query da 3 minuti a 2 secondi in SQL Server!
π Diagnosi: cosa succede davvero nel motore SQL?
Il primo passo è stato attivare SQL Server Profiler per intercettare la query esatta eseguita dal frontend. Usavano già una SqlQuery()
diretta, quindi niente Entity Framework a rallentare.
Con SET STATISTICS TIME, IO ON
ho raccolto le prime informazioni:
Più di 1.8 milioni di letture logiche,
CPU costantemente alta durante l'esecuzione,
Una vista complessa con JOIN annidati e funzioni scalari nella SELECT,
Uso di YEAR() e ISNULL() nelle clausole
WHERE
.
Il piano di esecuzione mostrava:
β Table scan completo su tabelle enormi
β Nested loop inefficienti
β Join su campi non indicizzati
π οΈ Analisi della query originale
Ecco una versione semplificata:
SELECTΒ u.Nome, ISNULL(f.Codice, '') AS CodiceFiscale, YEAR(u.DataCreazione) AS Anno, (SELECT COUNT(*) FROM Ordini o WHERE o.IdUtente = u.IdUtente) AS TotaleOrdini FROM Utenti u LEFT JOIN Fiscali f ON dbo.MySafeTrim(f.IdUtente) = u.IdUtente WHERE YEAR(u.DataCreazione) = 2023
Problemi principali:
Funzioni come
YEAR()
= non sargableFunzione
MySafeTrim
personalizzata = non indicizzabileSubquery in
SELECT
= costosa per ogni rigaNessun indice su
DataCreazione
oIdUtente
π§ Strategia di intervento: ottimizzare senza cambiare il risultato
L'obiettivo era mantenere lo stesso output ma ottimizzare tutto al massimo.
1. Query riscritta in forma sargable
SELECT u.Nome, f.Codice AS CodiceFiscale, o.TotaleOrdini FROM Utenti u LEFT JOIN Fiscali f ON f.IdUtente = u.IdUtente LEFT JOIN ( SELECT IdUtente, COUNT(*) AS TotaleOrdini FROM Ordini GROUP BY IdUtente ) o ON o.IdUtente = u.IdUtente WHERE u.DataCreazione >= '2023-01-01' AND u.DataCreazione < '2024-01-01'
β
Tolte le funzioni dalla WHERE
β
Sostituita la subquery correlata con un JOIN preaggregato
β
Evitato ISNULL()
se non indispensabile nel filtro
β
Rimossa funzione MySafeTrim
π§± Indici creati ad hoc
Utilizzando sys.dm_db_missing_index_details
ho individuato suggerimenti su tabelle usate senza indice.
π Indice su Utenti
per data:
CREATE NONCLUSTERED INDEX IX_Utenti_Data ON Utenti (DataCreazione) INCLUDE (Nome, IdUtente)
π Indice su Fiscali
per join rapida:
CREATE NONCLUSTERED INDEX IX_Fiscali_IdUtente ON Fiscali (IdUtente)
π Indice su Ordini
:
CREATE NONCLUSTERED INDEX IX_Ordini_IdUtente ON Ordini (IdUtente)
Inoltre ho suggerito all’azienda di ricostruire periodicamente gli indici per evitare la frammentazione, tramite job SQL Server Agent.
β±οΈ Confronto dei risultati
Versione | Tempo esecuzione | Letture logiche | Costi CPU |
---|---|---|---|
Prima | 3 min e 12 sec | 1.870.000 | Alta |
Dopo | 2.1 secondi | 32.000 | Bassa |
La query ora risponde in tempo reale anche con una base dati in crescita.
β Conclusioni e lezioni apprese
Questa esperienza dimostra alcune regole d’oro: