Una delle correzioni incluse nell'aggiornamento cumulativo 11 per SQL Server 2008 R2 Service Pack 2 risolve un "deadlock errato" che può verificarsi in uno scenario specifico (spiegato più avanti in questo articolo). Sfortunatamente, la correzione introduce un nuovo bug, in cui le query SELECT in RCSI (lettura dell'isolamento dello snapshot commesso) iniziano a prendere blocchi condivisi dall'intento a livello di tabella. Di conseguenza, potresti vedere un aumento del blocco (e potenzialmente deadlock) per le query RCSI dopo l'applicazione di 2008 R2 SP2 CU11 (o versioni successive).
Questa sarà una sorpresa indesiderata per chiunque sia abituato ai lettori che non bloccano gli scrittori (e viceversa) quando utilizzano RCSI. Non c'è alcuna correzione per il bug RCSI al momento della scrittura. In effetti, l'elemento Connect creato da Eugene Karpovich per segnalare il problema è stato chiuso come "Non risolverà", anche se capisco che questa decisione è attualmente in fase di revisione.
Normalmente, questo problema potrebbe non essere una preoccupazione così grande, perché gli aggiornamenti cumulativi in genere non vengono applicati così ampiamente come i service pack completi. Tuttavia, Microsoft ha recentemente annunciato che sarà disponibile un Service Pack 3 finale per SQL Server 2008 R2. Questo Service Pack sarà un semplice rollup di aggiornamenti cumulativi SP2 esistenti (fino a CU13 incluso) ma senza nuove correzioni. Il risultato di tutto questo è che, a meno che qualcosa non cambi nel frattempo, gli utenti che applicano SP3 inizieranno improvvisamente a essere colpiti dal bug RCSI introdotto in CU11.
modifica:poco prima della pubblicazione di questo articolo, Microsoft ha confermato che questa regressione verrà corretta in SP3.
Lo stesso bug "deadlock errato" (la cui correzione introduce il nuovo bug) è stato corretto anche nell'aggiornamento cumulativo 8 per SQL Server 2012 Service Pack 1 come descritto in KB2923460. La correzione per SQL Server 2012 è diversa e non introdurre il nuovo problema RCSI.
SQL Server 2014 non è mai stato interessato da nessuno dei due problemi, per quanto ne so. Non c'è certamente alcuna documentazione che indichi il contrario e i test che ho eseguito su RTM, CU1 e CU2 2014 non riproducono nessuno dei due bug.
Il bug RCSI R2 2008
Una query SELECT eseguita in RCSI richiede in genere solo un blocco di stabilità dello schema (Sch-S), che è compatibile con tutti gli altri blocchi ad eccezione di un blocco di modifica dello schema (Sch-M). Quando CU11 (o versione successiva) viene applicato a un'istanza di SQL Server 2008 R2, queste query iniziano a richiedere un blocco condiviso per intenzione (Tab-IS) a livello di tabella. Il seguente script di test può essere utilizzato per dimostrare la differenza nei comportamenti:
USE master; GO SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET NOCOUNT ON; GO CREATE DATABASE RCSI; GO ALTER DATABASE RCSI SET READ_COMMITTED_SNAPSHOT ON; GO ALTER DATABASE RCSI SET ALLOW_SNAPSHOT_ISOLATION OFF; GO USE RCSI; GO CREATE TABLE dbo.Test ( id integer IDENTITY NOT NULL, col1 integer NOT NULL, CONSTRAINT PK_Test PRIMARY KEY CLUSTERED (id) ); GO INSERT dbo.Test (col1) VALUES (1), (2), (3), (4); GO -- Show locks DBCC TRACEON (1200, 3604, -1) WITH NO_INFOMSGS; SELECT * FROM dbo.Test; DBCC TRACEOFF (1200, 3604, -1) WITH NO_INFOMSGS; GO ALTER DATABASE RCSI SET SINGLE_USER WITH ROLLBACK IMMEDIATE; USE master; DROP DATABASE RCSI;
Quando viene eseguito su un'istanza di SQL Server 2008 R2 senza il bug, l'output di debug mostra un singolo blocco Sch-S preso per l'istruzione di test come previsto:
Processo di acquisizione del blocco Sch-S su OBJECT:7:2105058535:0 risultato:OKProcesso di rilascio del blocco su OBJECT:7:2105058535:0
Quando viene eseguito su SQL Server 2008 R2 build 10.50.4302 (o versioni successive), l'output è simile a:
Processo acquisizione blocco IS su OBJECT:7:2105058535:0 risultato:OKProcesso rilascio blocco su OBJECT:7:2105058535:0
Si noti che il blocco Sch-S è stato sostituito da un blocco Tab-IS.
Implicazioni e mitigazioni
Un blocco con intento condiviso (IS) è ancora un blocco molto compatibile, ma non è così compatibile con la concorrenza come Sch-S. La matrice di compatibilità del blocco mostra che un blocco IS è in conflitto con:
- Sch-M (modifica dello schema) – come da Sch-S
- BU (aggiornamento collettivo)
- X (esclusivo)
L'incompatibilità con i blocchi esclusivi (X) significa che una lettura in RCSI si bloccherà se un processo simultaneo mantiene un blocco esclusivo sulla stessa risorsa. Allo stesso modo, uno scrittore che necessita di un blocco esclusivo si bloccherà se un lettore RCSI simultaneo detiene un blocco IS. I blocchi esclusivi vengono ottenuti ogni volta che i dati vengono modificati e mantenuti fino alla fine della transazione, quindi l'effetto del bug è che i lettori in RCSI verranno bloccati da autori simultanei (e viceversa) quando non lo erano prima dell'applicazione di CU11.
Un fattore attenuante significativo è che il bug causa solo un livello di tabella blocco intento condiviso da acquisire. Uno scrittore simultaneo che necessita di un livello di tabella il blocco esclusivo causerà il blocco (e potenzialmente un deadlock). Tuttavia, i writer simultanei che richiedono solo blocchi esclusivi a un livello inferiore (ad es. riga, pagina o partizione) non causare un blocco o un deadlock. A livello di tabella, questi writer acquisiranno solo un blocco IX (intent-exclusive), compatibile con Tab-IS. I blocchi esclusivi presi a livelli di granularità inferiori non causeranno un conflitto.
Nella maggior parte dei sistemi, i blocchi esclusivi a livello di tabella (Tab-X) saranno relativamente rari. A meno che non venga esplicitamente richiesto utilizzando un suggerimento TABLOCKX, alcune possibili cause di un blocco Tab-X sono:
- Blocca l'escalation da una granularità inferiore
- Utilizzo di SERIALIZABLE senza un indice di supporto per le serrature a intervallo di chiavi
Una soluzione tecnica consiste nell'aggiungere il suggerimento tabella (ridondante) WITH (READCOMMITTED)
a ogni tabella in ogni query eseguita in RCSI. Questo accade per aggirare il bug, quindi viene preso solo un blocco Sch-S, ma non è certo una proposta pratica.
Nonostante queste attenuazioni, l'utilizzo di Tab-IS per una query di sola lettura in RCSI è ancora un comportamento errato. Spero che possa essere risolto per SQL Server 2008 R2 prima del rilascio del Service Pack 3.
Il bug "Deadlock errato"
Come accennato in precedenza, il bug RCSI viene introdotto come effetto collaterale di una correzione per un bug di "deadlock errato". Questo problema precedente è documentato per SQL Server 2008 R2 in KB2929464 e per SQL Server 2012 in KB2923460. Nessuno dei due documenti è un modello di chiarezza (o accuratezza), ma il problema di fondo è piuttosto interessante, quindi voglio dedicare un po' di tempo a esaminarlo qui.
In sostanza, il deadlock si verifica quando:
- Tre o più transazioni simultanee lette dalla stessa tabella
- I suggerimenti UPDLOCK e TABLOCK vengono utilizzati in tutti e tre i casi
- L'impostazione del database READ_COMMITTED_SNAPSHOT è ATTIVA
Si noti che non importa a quale livello di isolamento vengono eseguite le transazioni. Per riprodurre il bug, esegui prima lo script di installazione di seguito:
USE master; GO SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO CREATE DATABASE IncorrectDeadlock; GO ALTER DATABASE IncorrectDeadlock SET READ_COMMITTED_SNAPSHOT ON; GO USE IncorrectDeadlock; GO CREATE TABLE dbo.Test ( id integer IDENTITY NOT NULL, col1 integer NOT NULL, CONSTRAINT PK_Test PRIMARY KEY CLUSTERED (id) ); GO INSERT dbo.Test (col1) VALUES (1);
Quindi, esegui il seguente script in tre connessioni separate (nota che la transazione viene lasciata aperta):
USE IncorrectDeadlock; GO SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO BEGIN TRANSACTION; SELECT T.id, T.col1 FROM dbo.Test AS T WITH (UPDLOCK, TABLOCK);
A questo punto la prima sessione avrà restituito un set di risultati e le altre due verranno bloccate. Il "deadlock errato" si verifica quando la prima sessione completa la transazione (committing o rollback). Quando ciò si verifica, una delle altre due sessioni segnalerà un deadlock:
Il deadlock si verifica perché le due sessioni bloccate in precedenza contengono Tab-IX (esclusivo per intento a livello di tabella) ed entrambi desiderano convertire il blocco in Tab-X (esclusivo a livello di tabella). Tab-IX è compatibile con un altro Tab-IX, ma non Tab-X. Questo è un deadlock di conversione (e l'ironia qui è che UPDLOCK viene spesso utilizzato per evitare deadlock di conversione).
Sentiti libero di variare il livello di isolamento della transazione per le tre query come desideri. Il deadlock si verificherà indipendentemente, fintanto che RCSI è abilitato, con gli stessi blocchi coinvolti. Al termine dei test, rimuovere il database dei test:
USE IncorrectDeadlock; ALTER DATABASE IncorrectDeadlock SET SINGLE_USER WITH ROLLBACK IMMEDIATE; USE master; DROP DATABASE IncorrectDeadlock;
Analisi e spiegazione
Personalmente non ricordo di aver mai usato UPDLOCK e TABLOCK insieme nel mio codice. Per me, questa combinazione di suggerimenti sembra intuitivamente strana perché SQL Server non ha un blocco di aggiornamento a livello di tabella . Quindi, cosa significa ancora significa specificare insieme i suggerimenti UPDLOCK e TABLOCK?
La documentazione dice questo:
UPDLOCKSpecifica che i blocchi di aggiornamento devono essere acquisiti e mantenuti fino al completamento della transazione. UPDLOCK accetta i blocchi di aggiornamento per le operazioni di lettura solo a livello di riga o di pagina. Se UPDLOCK è combinato con TABLOCK, o un blocco a livello di tabella viene preso per qualche altro motivo, verrà invece preso un blocco esclusivo (X).
Ciò suggerisce che la combinazione di suggerimenti dovrebbe tradursi in un unico blocco da tavolo esclusivo. In realtà, questa non è proprio tutta la storia:
In SQL Server 2000, la combinazione di suggerimenti UPDLOCK e TABLOCK comporta l'acquisizione di Tab-S (un blocco di tabella condivisa) seguita dalla conversione in Tab-X (blocco di tabella esclusivo) in tutti i livelli di isolamento eccetto READ UNCOMMITTED. Questa sequenza di blocchi può causare un deadlock in cui sono coinvolte tre o più sessioni:due sessioni acquisiscono Tab-S ed entrambe attendono l'altra per la conversione in Tab-X. In READ UNCOMMITTED, SQL Server 2000 accetta Sch-S quindi Tab-X, che non è soggetto a deadlock (solo normale blocco).
In SQL Server 2005 in poi (senza la correzione del bug) i blocchi presi dipendono solo se RCSI è abilitato o meno. Se RCSI è abilitato, tutti i livelli di isolamento prendi Tab-IX quindi converti in Tab-X. Questa sequenza provoca il deadlock degli indirizzi di correzione dei bug.
Se RCSI non è abilitato, i livelli di isolamento corrispondenti si comportano come in SQL Server 2000 (prendendo Tab-S e poi convertendo in Tab-X). Il livello di isolamento dello snapshot (nuovo per il 2005) richiede Sch-S seguito da Tab-X. Di conseguenza, SI e READ UNCOMMITTED sono gli unici livelli di isolamento non soggetti a questo deadlock nello scenario UPDLOCK, TABLOCK quando RCSI non è abilitato.
La correzione del deadlock
La correzione modifica i blocchi presi quando UPDLOCK e TABLOCK vengono specificati insieme, per tutti i livelli di isolamento e indipendentemente se RCSI è abilitato o meno. Dopo l'applicazione della correzione, UPDLOCK e TABLOCK fanno sì che il motore acquisisca Tab-SIX (condiviso a livello di tabella con finalità esclusive), che viene quindi convertito in Tab-X.
Ciò evita lo scenario di deadlock perché Tab-SIX non è compatibile con un altro Tab-SIX. Ricorda, il deadlock si è verificato quando due processi hanno tenuto Tab-IX in attesa di conversione in Tab-X. Con Tab-IX sostituito da Tab-SIX, non è possibile che entrambi contengano Tab-SIX contemporaneamente. Il risultato è un normale scenario di blocco invece di un deadlock.
Pensieri finali
La correzione del "deadlock errato" risolve un particolare scenario di deadlock, ma non provoca comunque il comportamento che immagino le persone che specificano UPDLOCK e TABLOCK previste. Se SQL Server avesse un blocco Tab-U (aggiornamento a livello di tabella), impedirebbe modifiche simultanee alla tabella ma consentirebbe lettori simultanei. Questo è quello che immagino sarebbe l'intento delle persone che usano questi suggerimenti insieme e posso vedere come potrebbe essere utile.
L'implementazione corrente (in cui alla fine viene presa Tab-X invece della Tab-U mancante) non corrisponde a questa aspettativa perché Tab-X impedisce letture simultanee (a meno che non venga utilizzato un livello di isolamento del controllo delle versioni delle righe). Potremmo anche specificare TABLOCKX in molti casi. Anche il fatto che la correzione introduca un nuovo bug (solo per gli utenti di SQL Server 2008 R2) è un peccato, in particolare se il bug verrà incluso in 2008 R2 SP3.
Si noti che la correzione del deadlock non viene resa disponibile per le versioni di SQL Server precedenti al 2008 R2. Queste versioni continueranno ad avere il complesso comportamento di blocco per UPDLOCK e TABLOCK come descritto sopra.
I miei ringraziamenti a Eugene Karpovich che per primo ha portato questo problema alla mia attenzione in un commento al mio articolo sulle modifiche dei dati sotto RCSI.