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
MySafeTrimpersonalizzata = non indicizzabileSubquery in
SELECT= costosa per ogni rigaNessun indice su
DataCreazioneoIdUtente
🧠 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: