Tipo di transazione IsolationLevel dovrebbe essere utilizzato per ignorare inserti ma bloccare la riga selezionata?
-
25-09-2019 - |
Domanda
Ho un processo che avvia una transazione, inserisce un record nella Tabella 1, e poi chiama un servizio Web in esecuzione lunga (fino a 30 secondi). Se la chiamata di servizio Web non riesce quindi l'inserto è rollback (che è quello che vogliamo). Ecco un esempio dell'inserto (in realtà è più inserti in più tabelle, ma sto semplificando per questa domanda):
INSERT INTO Table1 (UserId, StatusTypeId) VALUES (@UserId, 1)
Ho un secondo processo che interroga Table1 dal primo passo in questo modo:
SELECT TOP 1 * FROM Table1 WHERE StatusTypeId=2
e poi aggiorna la fila per un utente. Quando il processo è in esecuzione 1, Tabella 1 è bloccato: processo 2 non sarà completa fino a quando processo 1 finiture, che è un problema perché un lungo ritardo viene introdotto mentre il processo di 1 termina la sua chiamata al servizio Web.
Processo 1 sarà sempre e solo inserire uno StatusTypeId di 1 ed è anche l'unica operazione che inserisce in Tabella 1. Processo 2 sarà solo query StatusTypeId = 2. Voglio dire Processo 2 di ignorare eventuali inserti in Table1 ma bloccare la riga che seleziona. Il livello di isolamento predefinito per processo 2 è in attesa su troppo, ma ho una paura che IsolationLevel.ReadUncommitted permette la lettura dei dati sporchi troppo. Non voglio due utenti in esecuzione di processo 2 e poi accidentalmente ottenere la stessa riga.
C'è un IsolationLevel diverso da utilizzare diverso READUNCOMMITTED che dice ignorano righe inserite, ma assicurarsi che il selezionare le serrature della riga che è selezionata?
Soluzione
Per quanto riguarda il SELEZIONA bloccata dall'inserto questo dovrebbe essere evitato fornendo indici appropriati.
Tabella di prova.
CREATE TABLE Table1
(
UserId INT PRIMARY KEY,
StatusTypeId INT,
AnotherColumn varchar(50)
)
insert into Table1
SELECT number, (LEN(type)%2)+1, newid()
FROM master.dbo.spt_values
where type='p'
Query finestra uno
BEGIN TRAN
INSERT INTO Table1 (UserId, StatusTypeId) VALUES (5000, 1)
WAITFOR DELAY '00:01';
ROLLBACK
Finestra Query due (Blocchi)
SELECT TOP 1 *
FROM Table1
WHERE StatusTypeId=2
ORDER BY AnotherColumn
Ma se riprovare il test dopo l'aggiunta di un indice non bloccherà CREATE NONCLUSTERED INDEX ix ON Table1 (StatusTypeId,AnotherColumn)
Per quanto riguarda il tuo blocco di righe per Process 2
è possibile utilizzare il seguente (il suggerimento READPAST
permetterà 2 transazioni Process 2
concorrenti per iniziare la lavorazione di diversi file piuttosto che uno bloccando l'altro). Si potrebbe trovare questo articolo di Remus Ruşanu rilevanti
BEGIN TRAN
SELECT TOP 1 *
FROM Table1 WITH (UPDLOCK, READPAST)
WHERE StatusTypeId=2
ORDER BY AnotherColumn
/*
Rest of Process Two's code here
*/
COMMIT
Altri suggerimenti
Modifica: Avendo ri-leggere la domanda, il blocco su qualsiasi insert
non deve effettuare qualsiasi select
sotto READ COMMITTED
questo potrebbe essere un problema con gli indici.
Tuttavia, dai vostri commenti e resto della questione sembra desideri solo una transazione per essere in grado di leggere una riga alla volta, che non è ciò che un livello di isolamento previene.
Impediscono
-
Dirty Read
- lettura dei dati non impegnati in una transazione che potrebbe essere il rollback - si verifica inREAD UNCOMMITTED
, ha impedito aREAD COMMITTED
,REPEATABLE READ
,SERIALIZABLE
-
Non Repeatable Reads
- una riga viene aggiornata mentre viene letto in una transazione commit, cioè la stessa lettura di una riga specifica può verificarsi due volte in una transazione e produrre un risultati diversi - si verifica inREAD UNCOMMITTED
,READ COMMITTED
. impedito aREPEATABLE READ
,SERIALIZABLE
-
phantom rows
- una riga viene inserito o eliminato durante la lettura in una transazione UNCOMMITED, il che significa che la stessa lettura di più righe avvenga due volte in una transazione e produrre risultati diversi, con righe mancanti o aggiunti o - avviene inREAD UNCOMMITTED
,READ COMMITTED
,REPEATABLE READ
, impedito nelSERIALIZABLE