T-SQL & quando gli update non aggiornano nessuna riga

Carissimi lettori,

Oggi vorrei aprire una parentesi nella nostra carrellata di articoli che trattano delle novità di SQL Server 2019 per parlarvi di una domanda interessante che mi è stata posta qualche giorno fa.

La domanda era più o meno questa:

Cosa succede quando eseguo un UPDATE che non va a aggiornare nessun dato perchè il suo valore è già quello che io voglio impostare?

Un update che non aggiorna nessuna riga

Per fare un esempio prendiamo la tabella creata nello scorso articolo.
Supponiamo quindi che la colonna NOME abbia già il valore "LUCA"

Il nostro primo UPDATE sarebbe questo:


UPDATE ELENCOVENDITE
SET
  CLIENTE = 'HHH'
WHERE
  ID = 8


La domanda continuava chiedendo se, ai fini delle prestazioni, in questo caso non fosse meglio aggiungere una condizione nella clausola WHERE:

UPDATE ELENCOVENDITE
SET
  CLIENTE = 'HHH'
WHERE
  ID = 8 AND CLIENTE<> 'HHH'
    


Siete pronti per vedere cosa accade esattamente durante un update?


Seguitemi!

Analizzeremo prima il log delle transazioni e poi i lock generati nei vari casi.

Anzi, per trattare l'argomento in modo completo partiremo dal "caso base" in cui la prima query effettivamente va ad aggiornare la riga (quindi il campo CLIENTE <> 'HHH')


Caso base: il nostro update aggiorna una riga.

Osservando il log delle transazioni vediamo che sono presenti 9 righe di cui 4 sono rappresentate dal nostro update

SQL Server transaction log


Adesso eseguiamo il nostro UPDATE tracciandolo tramite il profiler estraendo gli eventi relativi ai lock.


Osserviamo che vengono acquisiti i seguenti lock :

1 lock a livello di OBJECT (5) di tipo IX (IX=intent exclusive)
5 lock a livello di PAGE(6) di cui 4 IX ed 1  IU (IU=intent update)
5 lock a livello di KEY (7) di cui 3 IX, 1 U e 1 RANGE I-N (U=update lock) (RANGE-I=insert range)


Caso in cui non viene aggiornato alcun valore

Ora confrontiamo i dati appena ottenuti con il caso in cui il nostro UPDATE non aggiorni nessuna riga: metto nel campo cliente un valore uguale a quello già presente.


La stringa SQL da eseguire è la stessa:


UPDATE ELENCOVENDITE
SET
  CLIENTE = 'HHH'
WHERE
  ID = 8


Vediamo il log delle transazioni:

SQL Server log delle transazioni


Il numero delle righe passa da 9 a 5 e se ci fate caso mancano proprio le 4 righe che derivano dal nostro update.

E questo cosa vuol dire?

Lo scrivo in blu proprio per evidenziare cosa occorre ricordarsi dall' articolo di oggi:

SQL Server è in grado di accorgersi che il valore già contenuto nella tabella è uguale a quello che gli vorremmo mettere! In questo caso non sovrascrive il valore in destinazione ma evita di scriverlo.

Indaghiamo però cosa succede a livello dei lock che vengono posti sui vari oggetti.
Questo aspetto è molto importante quando i vari statements T-SQL vengono eseguiti da più clients contemporaneamente.



Calano il numero di lock da 11 a 5 che comunque rimangono presenti.

Come accade nell'esempio precedente viene comunque posto un lock a livello di oggetto di tipo IX.
Sono poi presenti 2 blocchi a livello di pagina,uno IU ed uno IX
Sono infine presenti altri 2 blocchi a livello di key, uno di tipo U ed uno di tipo X.

1 lock a livello di OBJECT (5) di tipo IX (IX=intent exclusive)
2 lock a livello di PAGE(6) di cui 1 IX ed 1  IU (IU=intent update)
2 lock a livello di KEY (7) di cui 1 IX, 1 U (U=update lock)


Ultimo caso

Con l'ultimo caso vediamo cosa accade nella Query:

UPDATE ELENCOVENDITE
SET
  CLIENTE = 'HHH'
WHERE
  ID = 8 AND CLIENTE<> 'HHH'
    

Nel log delle transazioni appaiono solamente le tre righe evidenziate:


Mancano infatti le due righe di LOOP_BEGIN_XACT e LOOP_COMMIT_XACT

A livello di lock ne abbiamo 3 al posto di 5.


Che nel dettaglio sono:

1 lock a livello di OBJECT (5) di tipo IX (IX=intent exclusive)
1 lock a livello di PAGE(6) di tipo IU (IU=intent update)
1 lock a livello di KEY (7) di tipo U (U=update lock)


Bene, fino a questo punto abbiamo capito che la soluzione che prevende l'aggiunta nella clausola WHERE della condizione aggiuntiva sembra essere (in modo difficilmente quantificabile!) migliore.


UPDATE ELENCOVENDITE
SET
  CLIENTE = 'HHH'
WHERE
  ID = 8 AND CLIENTE<> 'HHH'
    


Però c'è un però!

Vediamo di confrontare le due Query tramite il piano di esecuzione.


Innanzitutto attenzione: aggiungere una (ulteriore) condizione ad una WHERE porta anche ad una elaborazione in qualche modo più pesante.

Se infatti osserviamo il piano di esecuzione sotto vediamo che la condizione aggiuntiva si traduce in un operatore di calcolo scalare.



E sorprendentemente alla prova pratica il tempo di esecuzione delle due Query è simile!


Traiamo quindi le conclusioni

La risposta all'interessante quesito è quindi purtroppo: dipende! 

Nel caso (semplice) che abbiamo utilizzato per fare la nostra analisi le differenze sono minime tanto che è difficile quantificarle.

In un caso più reale e più complesso dove sono in gioco, più colonne e dopo la where sono indicate molte condizione la strada migliore è quella di verificare direttamente la Query che stiamo utilizzantp. Solo così si capirà qual'è soluzione migliore!
 


Anche per oggi è tutto! Buon inizio di settimana! 
Luca


Luca Biondi @ SQLServerPerformance blog!







Next post: SQL Server e gli Extended Event

Previous post: SQL Server 2019 e la funzione Approx_Count_Distinct

Comments

I Post più popolari

SQL Server, execution plan and the lazy spool (clearly explained)

SQL Server, datetime vs. datetime2

La clausola NOLOCK. Approfondiamo e facciamo chiarezza!