SQL Server, dalle fondamenta Lezione 40 / 40

Health check di SQL Server in 30 minuti: lo script che esegui davvero

La checklist ordinata che eseguo su qualsiasi SQL Server che non ho mai visto prima. Stampabile, copiabile, e la lezione di chiusura del corso.

Ti consegnano le chiavi di un SQL Server che non hai mai visto prima. “Dicci cos’ha che non va entro le 17.”

Questa lezione è la checklist da 30 minuti che eseguo. Ogni query, in ordine, con quello che stai cercando e cosa fare se lo trovi. È la lezione di chiusura del corso perché tira insieme tutto quello che abbiamo coperto. Puoi stamparla. Puoi incollarla nella tua cassetta degli attrezzi. Il giorno in cui un nuovo cliente ti consegna un SQL Server, tiri fuori questa e in mezz’ora sei produttivo.

Grazie per aver seguito 40 lezioni. Chiudiamo con un giro pulito attraverso l’intero kit del DBA.

Step 1 — Panoramica del server (2 minuti)

SELECT
    @@SERVERNAME,
    @@VERSION,
    SERVERPROPERTY('Edition'),
    SERVERPROPERTY('ProductVersion'),
    SERVERPROPERTY('MachineName'),
    cpu_count, hyperthread_ratio, physical_memory_kb / 1024 AS physical_memory_mb,
    sqlserver_start_time
FROM sys.dm_os_sys_info;

Cosa stai cercando:

  • Versione di SQL Server — idealmente 2019+.
  • Edition — Enterprise sblocca più funzionalità.
  • Uptime — se < 1 settimana, le wait stats e la cache saranno fuorvianti.
  • Memoria — confronta con l’impostazione max server memory.

Step 2 — sp_Blitz (5 minuti)

EXEC sp_Blitz;

Leggi i finding di priorità 1-20. Triage:

  • P1 “No backup” → fix immediato.
  • P1 “Corruption” → fix adesso, vedi lezione 31.
  • P10 “SA account weak password” → annota, segnala a security.
  • P50 stranezze di configurazione → documenta, fix dopo.

Step 3 — Recovery model e stato dei backup (2 minuti)

SELECT d.name,
       d.recovery_model_desc,
       DATEDIFF(HOUR, MAX(CASE WHEN bs.type = 'D' THEN bs.backup_finish_date END), GETDATE()) AS hours_since_last_full,
       DATEDIFF(HOUR, MAX(CASE WHEN bs.type = 'L' THEN bs.backup_finish_date END), GETDATE()) AS hours_since_last_log
FROM sys.databases AS d
LEFT JOIN msdb.dbo.backupset AS bs ON bs.database_name = d.name
WHERE d.name NOT IN ('tempdb')
GROUP BY d.name, d.recovery_model_desc;

Cosa stai cercando:

  • FULL recovery senza log backup recente → il log si sta riempiendo; sistema o passa a SIMPLE.
  • SIMPLE recovery su un database business-critical → conferma che sia intenzionale.
  • Nessun full backup da > 48 ore → schedulane uno adesso.

Step 4 — Configurazione di tempdb (1 minuto)

SELECT name, size * 8 / 1024 AS size_mb, growth, is_percent_growth, type_desc
FROM tempdb.sys.database_files;

Cosa stai cercando:

  • File dati singolo → aggiungine altri (uno per vCPU fino a 8).
  • is_percent_growth = 1 con valori piccoli → cambia in MB assoluti.
  • Dimensioni piccole → pre-cresci a una working size attesa.

Step 5 — Wait stats (3 minuti)

(La query filtrata della lezione 36.) Cosa stai cercando:

  • PAGEIOLATCH_* in cima → disco lento o buffer pool sottodimensionato.
  • LCK_* in cima → problemi di blocking, vedi Step 8.
  • CXPACKET in cima → problemi di parallelismo; probabilmente non urgenti.
  • ASYNC_NETWORK_IO in cima → app che legge lentamente o result set enormi.
  • SOS_SCHEDULER_YIELD alto → pressione di CPU.

I wait in cima definiscono la prossima indagine.

Step 6 — sp_BlitzFirst per wait stats live (2 minuti)

EXEC sp_BlitzFirst @ExpertMode = 1;

Campionamento di 5 secondi dell’attività corrente. Ti dice cosa sta succedendo adesso (vs cumulativo dal restart).

Step 7 — Top query costose (3 minuti)

USE DBA;
EXEC sp_BlitzCache @Top = 20, @SortOrder = 'CPU';
EXEC sp_BlitzCache @Top = 20, @SortOrder = 'Reads';

Leggi la colonna warnings. Colpevoli comuni:

  • Implicit conversion → sistema la query o il tipo di colonna.
  • Key lookup pesanti → aggiungi un covering index.
  • Parameter sniffing → recompile o force tramite Query Store.
  • Spill in tempdb → più memoria o indice migliore.

Step 8 — Blocking corrente e attività live (1 minuto)

EXEC sp_WhoIsActive @find_block_leaders = 1, @get_plans = 1;

Cosa stai cercando:

  • Blocker long-running → decidi se killarli.
  • Sessioni che dormono con transazioni aperte → zombie; killa.
  • Query attive pesanti → correla con i top di CPU.

Step 9 — Audit degli indici (5 minuti)

USE Runehold;   -- adattare per ogni database utente
EXEC sp_BlitzIndex;

Cosa stai cercando:

  • P1 “Duplicate indexes” → droppa i duplicati.
  • Tabelle heap → aggiungi un clustered index.
  • Indici inutilizzati → candidati al drop (verifica nel tempo).
  • Suggerimenti di missing index → valuta, scegli i pochi a maggior impatto.

Step 10 — Postura di Query Store (1 minuto)

SELECT name, is_query_store_on
FROM sys.databases WHERE name NOT IN ('master', 'tempdb', 'model', 'msdb');

Se qualche database utente ha Query Store spento, accendilo (lezione 28). Senza, non puoi trovare facilmente le regressioni dopo.

Step 11 — File growth e uso del disco (2 minuti)

SELECT DB_NAME(mf.database_id) AS db, mf.name, mf.type_desc,
       mf.size * 8 / 1024 AS size_mb, mf.growth, mf.is_percent_growth
FROM sys.master_files AS mf
WHERE mf.database_id > 4
ORDER BY size_mb DESC;

Cosa stai cercando:

  • File di log con is_percent_growth = 1 → cambia in MB assoluti.
  • Growth fisso minuscolo (es. 10 MB) → portalo a una dimensione sensata.
  • Database più grandi → conferma che ci sia headroom sul disco.

Step 12 — Salute degli Agent job (2 minuti)

SELECT TOP (20)
    j.name, h.step_id,
    CASE h.run_status
        WHEN 0 THEN 'Failed' WHEN 1 THEN 'Succeeded' WHEN 2 THEN 'Retry'
        WHEN 3 THEN 'Cancelled' WHEN 4 THEN 'In Progress' END AS status,
    CONVERT(VARCHAR, msdb.dbo.agent_datetime(h.run_date, h.run_time), 120) AS run_time,
    h.message
FROM msdb.dbo.sysjobhistory AS h
JOIN msdb.dbo.sysjobs       AS j ON j.job_id = h.job_id
ORDER BY h.run_date DESC, h.run_time DESC;

Cosa stai cercando:

  • Failure recenti su job di backup o manutenzione.
  • Job che non girano da molto (schedule disabilitata? Agent fermo?).
  • Job “In Progress” che dovevano finire un’ora fa.

Step 13 — Verifica Ola Hallengren + First Responder Kit (1 minuto)

SELECT OBJECT_SCHEMA_NAME(object_id) + '.' + name AS proc_name
FROM DBA.sys.procedures
WHERE name IN ('IndexOptimize', 'DatabaseBackup', 'DatabaseIntegrityCheck',
               'sp_Blitz', 'sp_BlitzIndex', 'sp_BlitzCache',
               'sp_BlitzFirst', 'sp_WhoIsActive');

Installa quelli che mancano. Minuti spesi bene.

Step 14 — Storia dei deadlock (1 minuto)

-- Dalla lezione 20
SELECT TOP (10)
    XEvent.value('(@timestamp)[1]', 'DATETIME2') AS ts,
    XEvent.query('.')                              AS deadlock_xml
FROM (
    SELECT CAST(target_data AS XML) AS td
    FROM sys.dm_xe_session_targets st
    JOIN sys.dm_xe_sessions s ON s.address = st.event_session_address
    WHERE s.name = 'system_health' AND st.target_name = 'ring_buffer'
) AS source
CROSS APPLY td.nodes('RingBufferTarget/event[@name="xml_deadlock_report"]') AS X(XEvent)
ORDER BY ts DESC;

Cerchi deadlock recenti. Se sono > 1 al giorno, c’è qualcosa che vale la pena sistemare.

Step 15 — Scrivilo (5 minuti)

Prendi i tuoi appunti e trasformali in un report breve:

Runehold SQL Server Health Check — 2026-04-16

  • Generale: Giallo. Tre finding P1.
  • Critico:
    • DB Sales_archive in FULL recovery senza log backup da 7 giorni. File di log all’87%. Sistema oggi.
    • Nessun backup su Reporting_Staging da 48 ore. Causa: Agent job Stage Backup disabilitato. Riabilita.
  • Alto:
    • tempdb ha 1 file dati su un server da 8 core. Aspettati contesa di PAGELATCH. Aggiungi 7 file.
    • Implicit conversion nella top query CPU sp_GetOrdersByCountry. Sistema il tipo di dato.
  • Medio:
    • 22% degli indici non clustered inutilizzati da 30+ giorni. Valuta i drop.
    • Query Store disabilitato su 3 database utente. Abilita.
  • Basso:
    • Vecchia password sa (la policy dice rotazione a 90 giorni). Ruota.
    • 140+ righe di history degli Agent job più vecchie di un anno in msdb. Pulisci.
  • Follow-up:
    • Schedula un drill di restore trimestrale.
    • Installa gli script di Ola Hallengren.
    • Migra da SQL 2019 a 2022 — già supportato.

Quello è il tuo deliverable. Breve, azionabile, prioritizzato.

Cosa viene dopo questo corso

Adesso conosci l’equivalente di un anno di esperienza on-the-job su SQL Server, compresso in 40 lezioni. Cosa fare dopo:

  1. Leggi il blog di Brent Ozar — post settimanali su tutto SQL Server. Pieno di opinioni, pratico, divertente.
  2. Il blog di Paul Randal a SQLskills — dive tecnici profondi sullo storage engine, la corruzione e DBCC. L’esperto degli internal.
  3. Il blog e i video di Erik Darling — consigli di query tuning con opinioni forti, parecchia magia con CTE e funzioni di finestra.
  4. brentozar.com/training — il team di Brent organizza training. Eccellenti se puoi permetterteli o se il datore di lavoro paga.
  5. Contribuisci a open source — gli script di Ola, il First Responder Kit, sp_WhoIsActive accettano tutti pull request.
  6. Fai pratica su un workload reale — una sandbox con Adventure Works o un tuo dataset. Esegui l’health check. Sistema i finding. Ripeti mensilmente.

Congratulazioni

Hai finito 40 lezioni di SQL Server. Adesso conosci:

  • Il motore (Moduli 1-3).
  • Indicizzazione e performance (Moduli 4-5).
  • Storage e partitioning (Modulo 6).
  • Gli essenziali del DBA — backup, Agent, sicurezza, tempdb, manutenzione, DMV (Modulo 7).
  • Il toolkit d’emergenza — First Responder Kit e sp_WhoIsActive (Modulo 8).

Se questo corso ha fatto il suo lavoro, sei pronto ad aprire un SQL Server che non hai mai visto, diagnosticarlo in 30 minuti, sistemare i problemi principali, e non piangere alle 3 di notte quando ti chiama. Quella è l’asticella. Quello significa “conoscere SQL Server” a questo livello.

Vai a prenderti cura dei tuoi sistemi di produzione. Dipendono da te.

— Grazie per aver letto. Se hai trovato errori, hai suggerimenti, o vuoi discutere su MERGE, scrivimi.

Cerca