Optimized Locking è una funzionalità dell’Engine di SQL Server 2025, è stata implementata per ridurre l’utilizzo della memoria impiegata per la gestione dei lock, ridurre il fenomeno noto con il nome di lock escalation e aumentare la concorrenza dei carichi di lavoro.
In questo articolo abbiamo descritto il funzionamento generale di Optimized Locking, ora passiamo alla pratica, osservando come l’Engine gestisce aggiornamenti concorrenti con Lock After Qualification. Consideriamo la tabella dbo.SalesOrder in un database SQL Server 2025 con Optimized Locking completamente abilitato. La tabella contiene i seguenti dati di prova.
| SalesOrderID | Status | SalesOrderNumber | CustomerID | TotalDue |
| 1 | N | SO-10001 | 123 | 1500 |
| 2 | P | SO-10002 | 123 | 2200 |
| 3 | N | SO-10003 | 123 | 450 |
| 4 | S | SO-10004 | 124 | 800 |
| 5 | C | SO-10005 | 125 | 600 |
Consideriamo i seguenti UPDATE sulla tabella dbo.SalesOrder: una sessione di lavoro (sessione 1) esegue un primo UPDATE; mentre la transazione è ancora attiva, una seconda sessione di lavoro (sessione 2) tenta di aggiornare le stesse righe.
Come gestirà la situazione con Optimized Locking attivo?
-- Sessione 1
BEGIN TRANSACTION
UPDATE
[dbo].[SalesOrder]
SET
Status = 'S'
WHERE
(CustomerID = 123)
AND (TotalDue > 1000);
-- Sessione 2
BEGIN TRANSACTION
UPDATE
[dbo].[SalesOrder]
SET
Status = 'C'
WHERE
(CustomerID = 123)
AND (TotalDue > 1000);
GO
Entrambi gli UPDATE coinvolgono le stesse righe. Osserviamo quindi i lock attivi con la DMV sys.dm_tran_locks la sessione 1 ha lo SPID 156, mentre la sessione 2 ha lo SPID 149.
SELECT
resource_type
,resource_database_id
,resource_description
,request_mode
,request_type
,request_status
,request_session_id
,resource_associated_entity_id
FROM
sys.dm_tran_locks
WHERE
request_session_id IN (156, 149)
AND resource_type IN ('PAGE', 'RID', 'KEY', 'XACT');
La figura seguente illustra i lock acquisiti dai comandi di UPDATE.

Nella prima riga, il lock acquisito dalla sessione 1 (SPID 156) sul TID è facilmente riconoscibile. Osservando la seconda riga, però, sorge una domanda: perché SQL Server richiede un lock di tipo Shared (S) sul TID invece di un lock di tipo Update (U), che sembrerebbe più appropriato per un’operazione di aggiornamento?
Per rispondere, è necessario comprendere la semantica dei lock sui TID e in che modo differisce da quella dei lock tradizionali (U/X/IX) applicati alle righe.
Semantica dei lock tradizionali
Nel modello classico, il lock di tipo Update (U) serve a evitare deadlock read-write (esempio due sessioni che leggono la stessa riga e poi entrambe vogliono aggiornarla), viene convertito in lock di tipo Exclusive (X) sulle righe qualificate per le modifiche. Il lock di tipo Shared (S) è utilizzato per le letture condivise, bloccato da altri X lock.
Semantica nel modello TID Locking
Con SQL Server 2025, i lock su risorse XACT non rappresentano blocchi su dati, ma dipendenze tra transazioni, quindi un lock di tipo Shared (S) su XACT non serve a proteggere dati condivisi, ma a monitorare lo stato di un’altra transazione. È un “wait handle” interno che SQL Server usa per sapere quando quella transazione termina. In altre parole il lock Shared posto sul TID che ha modificato le righe è passivo: non blocca, ma osserva. Quando la transazione proprietaria del TID viene completata (COMMIT o ROLLBACK), l’Engine rilascia il XACT lock e la seconda transazione viene svegliata. L’attesa viene tracciata con il wait type LCK_M_S_XACT_MODIFY.
L’attributo virtuale %%lockres%% , disponibile anche nelle versioni precedenti, mostra l’identificatore di lock (lock resource string) associato a ciascuna riga della tabella; vediamo gli identificatori di lock per le righe della tabella dbo.SalesOrder con la query seguente.
SELECT
%%lockres%% AS [Lockres]
,*
FROM
[dbo].[SalesOrder];
| Lockres | SalesOrderID | Status | SalesOrderNumber | CustomerID | TotalDue |
| (8194443284a0) | 1 | N | SO-10001 | 123 | 1500 |
| (61a06abd401c) | 2 | P | SO-10002 | 123 | 2200 |
| (98ec012aa510) | 3 | N | SO-10003 | 123 | 450 |
| (a0c936a3c965) | 4 | S | SO-10004 | 124 | 800 |
| (59855d342c69) | 5 | C | SO-10005 | 125 | 600 |
Si osservi che l’identificatore (8194443284a0) è lo stesso citato nella colonna resource_description della DMV sys.dm_tran_locks. La sessione 2, quella con SPID 149, sta aspettando sul Transaction ID che detiene la riga identificata dal lock hash (8194443284a0), la prima delle due righe modificate.
Perché non un Update lock?
Un Update (U) lock avrebbe senso solo se il lock rappresentasse una potenziale modifica futura di una risorsa condivisa. Ma in questo caso:
- La risorsa non è la riga, ma il Transaction ID
- Non c’è alcun rischio di “upgrade conflict” (solo una transazione può avere quel TID)
- Non esistono U lock semantici sulle risorse di tipo XACT ma solo S e X
Dal punto di vista del lock manager, un Update (U) lock non avrebbe alcun significato operativo su una risorsa di tipo XACT. La scelta di Microsoft di usare uno Shared (S) lock deriva da una logica interna: “sto aspettando di leggere lo stato della transazione che detiene quella riga”, non “mi preparo a modificarla”.
Riqualifica o restart
Dopo che la transazione bloccante è terminata, SQL Server rilegge la riga per verificare se è cambiata (il TID indica se c’è stata modifica). Se la riga è cambiata viene riqualificata rispetto alla condizione WHERE. Se l’operatore non supporta la riqualificazione, SQL Server riavvia l’intera query. Se la riga non è stata cambiata (il TID è lo stesso di prima e i dati sono identici), la riqualifica non avviene, viene acquisito un lock di tipo Exclusive (X) sulla riga, quest’ultima viene aggiornata e il lock rilasciato immediatamente dopo l’aggiornamento.
Conclusioni
Ciò che può sembrare un comportamento insolito, come la richiesta di un S lock su un TID, riflette in realtà la logica interna di Optimized Locking. SQL Server 2025 ottimizza così la gestione dei lock durante gli aggiornamenti concorrenti, bilanciando correttamente consistenza, scalabilità e performance.
UGISS User Group Italiano SQL Server