Passa al contenuto

Utilizzo della clausola NOLOCK in SQL Server

Vantaggi, criticità e utilizzo consapevole di questa clausola.

In SQL Server è possibile utilizzare specifiche clausole a livello di tabella (table hint) per modificare il comportamento predefinito dell’ottimizzatore durante l’esecuzione di una query. Una delle clausole più utilizzate, e spesso abusate, è WITH(NOLOCK).


Molto frequentemente presso istanze dei nostri clienti ho riscontrato la presenza di numerose query di tipo SELECT accompagnate da NOLOCK, spesso implementate senza una reale comprensione delle implicazioni che questo può comportare. Questo documento analizza il funzionamento di tale clausola a livello di engine SQL Server, i suoi vantaggi in termini di performance, le sue criticità dal punto di vista dell’integrità dei dati, e fornisce indicazioni su un utilizzo più consapevole basato su casi d’uso specifici.

Sintassi e funzionamento interno  

Sintassi della clausola NOLOCK 

La clausola NOLOCK può essere utilizzata esclusivamente nelle query di tipo SELECT, con la seguente sintassi:

SELECT *
FROM Tabella WITH (NOLOCK);

-- Sintassi con più tabelle 
SELECT a.Col1
	,b.Col2
FROM TabellaA AS a WITH (NOLOCK)
INNER JOIN TabellaB AS b WITH (NOLOCK) ON a.Id = b.Id;

Non è possibile utilizzarla in operazioni di UPDATE o DELETE, poiché queste operazioni richiedono necessariamente l’acquisizione di lock esclusivi. 

Meccanismo di funzionamento interno 

Utilizzando NOLOCK, SQL Server opera nei seguenti modi: 

  1. Ignora i lock di tipo S (Shared) e X (Exclusive) presenti sulle righe, pagine e tabelle.
  2. Non acquisisce lock di tipo S durante la lettura delle righe. 

Tecnicamente, questa clausola sovrascrive il livello di isolamento predefinito (READ COMMITTED) dell’istanza SQL Server, simulando il livello READ UNCOMMITTED specificamente per la singola query. Questo equivale a impostare temporaneamente il livello di isolamento della sessione utilizzando: 

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

-- Query 
SELECT *
FROM Tabella;

-- Ripristino 
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

Impatto sul sistema di lock di SQL Server


Sistema di lock in SQL Server


Per comprendere a fondo le implicazioni di NOLOCK, è necessario esaminare il sistema di lock di SQL Server:

Tipo di Lock Descrizione Compatibile con NOLOCK
S (Shared) Lock di lettura, permette letture concorrenti Ignorato da NOLOCK
U (Update) Lock preliminare per aggiornamento Ignorato da NOLOCK
X (Exclusive) Lock esclusivo per modifica Ignorato da NOLOCK
IS (Intent Shared) Indica l’intenzione di acquisire lock S a livelli inferiori Ignorato da NOLOCK
IX (Intent Exclusive) Indica l’intenzione di acquisire lock X a livelli inferiori Ignorato da NOLOCK
SCH-S (Schema Stability) Previene operazioni DDL concorrenti Acquisito da NOLOCK
SCH-M (Schema Modification) Per modifiche allo schema In conflitto con SCH-S
BU (Bulk Update) Per operazioni di bulk insert Ignorato da NOLOCK

Gerarchia dei lock e granularità 

SQL Server implementa una gerarchia di lock che va dal livello più ampio (DATABASE) al più granulare (ROW): 

DATABASE → TABLE → PAGE → ROW 

Con NOLOCK, la query ignora i lock a tutti i livelli eccetto quello di schema (SCH-S), il che permette di leggere tabelle anche mentre vengono modificate da altre sessioni. 

Controindicazioni nell’uso di NOLOCK 

L’uso di NOLOCK potrebbe sembrare quindi sempre conveniente e  performante. Tuttavia esistono degli effetti collaterali che vanno ben valutati per poterne fare un uso consapevole. 

1. Dirty Read (Lettura Sporca) 

Una dirty read si verifica quando una query con NOLOCK legge dati non ancora confermati (COMMIT). Se la transazione originaria viene annullata (ROLLBACK), la query avrà letto dati non validi e potenzialmente inconsistenti. 

Scenario tecnico

-- Sessione 1: Avvia una transazione e aggiorna il dato 
BEGIN TRANSACTION;

UPDATE Products
SET Price = 500
	,ModifiedDate = GETDATE()
WHERE ProductID = 1;

-- Pausa prima del commit o rollback 
-- Sessione 2: Legge il dato aggiornato con NOLOCK 
SELECT ProductID
	,Price
	,ModifiedDate
FROM Products WITH (NOLOCK)
WHERE ProductID = 1;

-- Output: Price = 500 (ma la transazione non è confermata!) 
-- Sessione 1: Annulla la transazione 
ROLLBACK;

Se la Sessione 2 ha già letto il valore 500, significa che ha ottenuto un dato mai confermato e potenzialmente errato. In un’applicazione reale, questo può portare a:

  • Calcoli errati (es. totali di ordini)
  • Decisioni operative basate su dati non definitivi
  • Report con dati inconsistenti


2. Non-Repeatable Read (Lettura Non Ripetibile)

Una non-repeatable read si verifica quando una query legge più volte lo stesso dato all’interno di una transazione e ottiene risultati diversi a causa di una modifica effettuata da un’altra transazione.

Scenario tecnico

-- Sessione 1: Avvia una transazione e legge il valore iniziale 
BEGIN TRANSACTION;

SELECT ProductID
	,Price
	,Quantity
FROM Products
WHERE ProductID = 1;

-- Supponiamo che Price = 100, Quantity = 10 
-- Sessione 2: Aggiorna il valore e conferma 
UPDATE Products
SET Price = 200
	,Quantity = 5
WHERE ProductID = 1;

COMMIT;

-- Sessione 1: Calcola il valore totale del prodotto 
DECLARE @TotalValue DECIMAL(18, 2);

SELECT @TotalValue = Price * Quantity
FROM Products WITH (NOLOCK)
WHERE ProductID = 1;
	-- Output: @TotalValue = 1000 (200 * 5), ma la sessione 1 aveva inizialmente letto 100 e 10! 

Questo scenario è particolarmente problematico quando: 

  • Si eseguono calcoli in più passaggi basati su dati correlati.
  • Si prendono decisioni dentro transazioni in base a letture multiple dello stesso dato.


3. Phantom Read (Lettura Fantasma)

Una phantom read si verifica quando una query eseguita più volte all’interno della stessa transazione restituisce un insieme diverso di righe a causa di un’inserzione o cancellazione di dati effettuata da un’altra transazione.


Scenario tecnico

-- Sessione 1: Inizia transazione e conta gli ordini di un cliente 
BEGIN TRANSACTION;

SELECT COUNT(*) AS NumOrders
FROM Orders
WHERE CustomerID = 10;

-- Output: 5 ordini 
-- Sessione 2: Inserisce un nuovo ordine e conferma 
INSERT INTO Orders (
	OrderID
	,CustomerID
	,OrderDate
	)
VALUES (
	101
	,10
	,GETDATE()
	);

COMMIT;

-- Sessione 1: Calcola valore medio degli ordini 
SELECT SUM(OrderAmount) / COUNT(*) AS AverageOrderValue
FROM Orders WITH (NOLOCK)
WHERE CustomerID = 10;
	-- Il denominatore ora sarà 6, non 5 come nella prima lettura 

4. Lost Updates (Aggiornamenti Perduti)

Un problema grave, seppur indiretto, legato all’uso di NOLOCK è il rischio di lost updates quando si legge un dato con NOLOCK e poi lo si aggiorna basandosi su tale lettura.


Scenario tecnico

-- Sessione 1: Legge un saldo con NOLOCK (potenzialmente dirty) 
DECLARE @Balance DECIMAL(18, 2);

SELECT @Balance = AccountBalance
FROM Accounts WITH (NOLOCK)
WHERE AccountID = 1001;

-- Supponiamo @Balance = 1000 
-- Intanto, la Sessione 2 aggiorna e conferma 
UPDATE Accounts
SET AccountBalance = 1500
WHERE AccountID = 1001;

COMMIT;

-- Sessione 1: Aggiorna basandosi sul valore letto (potenzialmente obsoleto) 
UPDATE Accounts
SET AccountBalance = @Balance + 200 -- Risulterà 1200 invece di 1700! 
WHERE AccountID = 1001;

5. Problemi di scan inconsistenti 

Un aspetto meno noto, ma altrettanto critico, riguarda la possibilità che SQL Server esegua letture inconsistenti delle strutture fisiche delle tabelle (pagine di dati, indici) durante l’esecuzione di una query con NOLOCK. 

Problemi specifici 

  1. Missing Rows (Righe Mancanti): Una query con NOLOCK potrebbe saltare righe durante la scansione se le pagine vengono riorganizzate da operazioni concorrenti. 
  2. Duplicate Rows (Righe Duplicate): La stessa riga potrebbe essere letta più volte se le pagine vengono divise durante la lettura. 
  3. 511 Errors: SQL Server può generare errori 511 quando incontra inconsistenze strutturali durante scansioni NOLOCK. 
-- Esempio che può provocare il problema: 
-- Sessione 1: Query di lunga durata con NOLOCK 
SELECT *
FROM LargeTable WITH (NOLOCK)
WHERE DateColumn < GETDATE();

-- Sessione 2: Contemporaneamente, operazione di riorganizzazione 
ALTER INDEX IX_LargeTable_DateColumn ON LargeTable REORGANIZE;

Query con NOLOCK e interazioni con i lock di schema 

Sebbene l’utilizzo di NOLOCK permetta di evitare lock esistenti su righe e pagine, è importante notare che SQL Server acquisisce comunque un lock di tipo SCH-S (Schema Stability) sulla tabella interrogata. Questo lock è necessario per garantire la stabilità dello schema e prevenire modifiche alla struttura della tabella durante l’esecuzione della query. 

Matrice di compatibilità dei lock di schema 

SCH-S SCH-M
SCH-S Compatibile Non compatibile
SCH-M Non compatibile Non compatibile

Scenari di blocco con operazioni DDL

Il problema si presenta quando altre transazioni cercano di acquisire un lock di tipo SCH-M (Schema Modification), necessario per operazioni come:

  • ALTER TABLE
  • ALTER INDEX
  • CREATE INDEX
  • DROP INDEX
  • DROP TABLE
  • TRUNCATE TABLE


In questi casi:

  1. Se una query con NOLOCK è in esecuzione e ha acquisito un lock SCH-S, impedirà a un’operazione DDL di acquisire il lock SCH-M necessario.
  2. Se un’operazione DDL è in attesa del lock SCH-M, tutte le nuove query con NOLOCK che necessitano di acquisire un lock SCH-S rimarranno in attesa.
-- Sessione 1: Query di lunga durata con NOLOCK 
SELECT COUNT(*)
FROM VeryLargeTable WITH (NOLOCK);

-- Acquisisce SCH-S, dura diversi minuti 
-- Sessione 2: Tentativo di ricostruzione indice (richiede SCH-M) 
ALTER INDEX ALL ON VeryLargeTable REBUILD;
	-- Rimane in attesa del rilascio di SCH-S 

Questo scenario è particolarmente problematico durante le operazioni di manutenzione notturna, quando spesso si eseguono:  

  • Job di reportistica con NOLOCK.
  • Job di manutenzione indici e statistiche che necessitano di SCH-M.

Alternative tecniche a NOLOCK 

Esistono soluzioni più sicure e spesso altrettanto performanti rispetto all’uso indiscriminato di NOLOCK: 

1. Read Committed Snapshot Isolation (RCSI) 

RCSI offre la maggior parte dei vantaggi prestazionali di NOLOCK senza le problematiche di integrità dei dati:

-- Abilitazione a livello di database 
ALTER DATABASE YourDatabase

SET READ_COMMITTED_SNAPSHOT ON;

RCSI funziona mantenendo versioni dei dati nel TempDB, permettendo alle query di leggere una snapshot consistente senza bloccare o essere bloccate da operazioni di scrittura.


Vantaggi di RCSI rispetto a NOLOCK:

  • Elimina dirty read
  • Elimina non-repeatable read
  • Mantiene la consistenza transazionale
  • Offre prestazioni simili in molti scenari


Limitazioni di RCSI:

  • Richiede più spazio su TempDB
  • Può aumentare la contesa su TempDB
  • Non risolve i phantom read


2. Snapshot Isolation Level

Un livello di isolamento ancora più elevato:

-- Abilitazione a livello di database 
ALTER DATABASE YourDatabase

SET ALLOW_SNAPSHOT_ISOLATION ON;
-- Uso in una transazione specifica 
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;

BEGIN TRANSACTION;

-- Query 
COMMIT;

Vantaggi di Snapshot Isolation: 

  • Elimina dirty read, non-repeatable read e phantom read 
  • Offre consistenza transazionale completa 
  • Prestazioni migliori di READ COMMITTED in scenari con alta concorrenza 

Limitazioni: 

  • Maggiore utilizzo di TempDB 
  • Possibili errori di update conflict 

3. Ottimizzazione degli indici e delle transazioni 

Una strategia complementare consiste nel migliorare le prestazioni attraverso un’adeguata indicizzazione, come per esempio covering indexes che rendono più veloci le query e generano meno lock sugli oggetti coinvolti. 

Inoltre, è importante cercare di mantenere aperte delle transazioni per il tempo strettamente necessario. Durante una transazione tutti i lock che vengono generati sugli oggetti coinvolti vengono mantenuti fintanto che la transazione non viene chiusa. E’ fondamentale quindi inserire in una singola transazione le sole operazioni di cui è strettamente necessario garantire l’esecuzione “tutto o niente”, in modo tale da liberare le risorse occupate il prima possibile.  

Casi d’uso appropriati per NOLOCK 

Nonostante i rischi, esistono scenari in cui l’uso di NOLOCK può essere appropriato: 

  1. Query di sola reportistica dove: 
  • L’inconsistenza temporanea dei dati è accettabile.
  • I report indicano chiaramente che i dati sono “point-in-time” e potenzialmente incompleti.
  • Si applicano aggregazioni statistiche che sono poco sensibili a piccole variazioni.

  2. Query di monitoraggio o diagnostica dove: 

  • Lo scopo è ottenere una panoramica approssimativa del sistema.
  • Il tempo di risposta è più importante della precisione assoluta.
  • I dati non vengono utilizzati per decisioni critiche.

  3. Query su tabelle di riferimento quasi statiche dove: 

  • Le modifiche sono rare e programmate.
  • Le query vengono eseguite in momenti in cui non avvengono modifiche.
  • L’impatto di eventuali letture sporche è minimo.

Conclusioni e best practices 

L’uso della clausola NOLOCK in SQL Server può risultare utile in specifici scenari per migliorare le prestazioni, ma deve essere impiegata con consapevolezza e parsimonia. I rischi di inconsistenza dei dati sono elevati, e nella maggior parte dei casi esistono alternative migliori che garantiscono un compromesso ottimale tra prestazioni e integrità dei dati. 

Linee guida per l’uso responsabile: 

  1. Valutare alternative: Prima di utilizzare NOLOCK, considerare RCSI, ottimizzazione degli indici, e altre soluzioni meno problematiche. 
  2. Documentare l’uso: Quando si utilizza NOLOCK, commentare il codice indicando il motivo della scelta e i potenziali rischi. 
  3. Test approfonditi: Verificare che l’applicazione gestisca correttamente le possibili inconsistenze dei dati. 
  4. Monitoraggio continuo: Implementare sistemi di rilevamento per identificare problemi di inconsistenza dei dati. 
  5. Revisione periodica: Rivalutare regolarmente le query che utilizzano NOLOCK per verificare se siano ancora necessarie o se esistano alternative migliori. 
  6. Formare i team: Assicurarsi che gli sviluppatori e i DBA comprendano le implicazioni dell’uso di NOLOCK. 

Prima di applicare NOLOCK indiscriminatamente, è essenziale valutare attentamente se un miglioramento dell’indicizzazione o un diverso livello di isolamento possano rappresentare una soluzione più efficace e sicura che garantisca sia prestazioni ottimali che integrità dei dati. 

di Matteo Dal Bianco, pubblicato il 26 marzo 2026

SQL Server: attach di un database senza il file di transaction log
Una miniguida a come ripristinare correttamente e in modo sicuro un database quando si è perso accidentalmente il transaction log.