Identificare le risorse lockate in SQL Server con Transact-SQL

Fortunatamente mi è capitato abbastanza di rado che una stored procedure o una query rimanga apparentemente bloccata causando rallentamenti e deadlock: quando succede però di solito è panico anche perché il primo problema che ci si trova a dover affrontare è “e adesso come capisco cosa mi sta bloccando?”

È quindi essenziale sapere come identificare le risorse bloccate, che possono essere tabelle, pagine o righe.

Il metodo più diretto per identificare i lock attivi passa tramite le Dynamic Management Views (DMV). Le più utilizzate sono:

sys.dm_tran_locks: mostra tutti i lock attualmente in essere
sys.dm_exec_requests: evidenzia sessioni attive e eventuali blocchi in corso
sys.dm_exec_sessions: offre dettagli sulle sessioni SQL attive

Per visualizzare tutte le risorse bloccate può tornare utile una query come questa:

SELECT
/*Tipo di risorsa su cui il lock è applicato. OBJECT (tabella o vista), PAGE (pagina di dati), KEY (chiave indice), RID (riga) ecc.*/
tl.resource_type,
/* ID del database dove si trova la risorsa lockata. Utile per filtrare lock su uno specifico database. */

tl.resource_database_id,
/*Nome del database */
DB_NAME(tl.resource_database_id) AS database_name,
/* Identificativo associato alla risorsa. Ad esempio, se resource_type = OBJECT, qui troviamo l’object_id della tabella coinvolta; se PAGE, viene indicato l’ID di quella pagina. */
tl.resource_associated_entity_id AS object_id,
/* Se RESOURCE_NAME = 'OBJECT' è il nome dell’oggetto (tabella o vista) associato al lock */
CASE WHEN tl.resource_type = 'OBJECT' THEN OBJECT_NAME(tl.resource_associated_entity_id, tl.resource_database_id) ELSE NULL END AS object_name,
/* Tipo di lock richiesto o detenuto, ad esempio: S (shared), X (exclusive), IX (intent exclusive), IS (intent shared), ecc. */
tl.request_mode AS lock_type,
/* Stato della richiesta di lock: può essere GRANTED (lock attivo) o WAIT (lock in attesa).*/
tl.request_status,
/* ID della sessione SQL che detiene o richiede il lock.*/
tl.request_session_id,
/* Nome del computer client da cui è partita la sessione.*/
es.host_name,
/* Nome del programma o applicativo che ha aperto la connessione */
es.program_name,
/* Se il lock sta bloccando o è bloccato da un'altra sessione, qui troviamo l’ID della sessione che causa il blocco. */
er.blocking_session_id
FROM sys.dm_tran_locks AS tl
LEFT JOIN sys.dm_exec_sessions AS es ON tl.request_session_id = es.session_id
LEFT JOIN sys.dm_exec_requests AS er ON tl.request_session_id = er.session_id
WHERE tl.resource_database_id = DB_ID()
ORDER BY tl.request_session_id, tl.resource_type

Che produrrà un output simile a questo:

Ho descritto brevemente le colonne nello script; qui aggiungo che le colonne più rilevanti ai nostri fini sono lock_type, request_session_id e blocking_session_id.

Nello specifico le risorse che presentano un valore NOT NULL nella colonna blocking_session_id vengono bloccate dalle risorse che hanno quel valore nella colonna request_session_id e in particolare quelle che hanno lock_type = 'X'.

A questo punto per sbloccare le risorse, si può usare il comando KILL (qualcuno ha detto Unix?); quindi ad esempio nel nostro caso:

KILL 63

Alcune precisazioni però sono necessarie.

Innanzitutto questa pratica deve costituire l’eccezionalità. Nella prassi normale questo non dovrebbe mai essere necessario; per evitarlo innanzitutto verificare se nel nostro codice SQL (penso a stored procedure, funzione e trigger) non abbiamo lasciato transazioni aperte.

Una buona pratica potrebbe anche essere quella di limitare il tempo lasciato alle query prima di andare in timeout; questo può essere fatto a livello di impostazioni di Sql Server Management Studio oppure nelle impostazioni delle connessioni nei programmi .NET oppure nelle query tramite il comando

SET LOCK_TIMEOUT 5000 -- in millisecondi