TL; DR: La domanda che segue si riduce a: Quando si inserisce una riga, esiste una finestra di opportunità tra la generazione di un nuovo Identity
valore e il blocco della chiave di riga corrispondente nell'indice cluster, in cui un osservatore esterno potrebbe vedere un nuovo Identity
valore inserito da una transazione concorrente? (In SQL Server.)
Versione dettagliata
Ho una tabella di SQL Server con una Identity
colonna chiamata CheckpointSequence
, che è la chiave dell'indice cluster della tabella (che ha anche un numero di indici aggiuntivi non cluster). Le righe vengono inserite nella tabella da numerosi processi e thread simultanei (a livello di isolamento READ COMMITTED
e senza IDENTITY_INSERT
). Allo stesso tempo, ci sono processi che leggono periodicamente le righe dall'indice cluster, ordinati per quella CheckpointSequence
colonna (anche a livello di isolamento READ COMMITTED
, con l' READ COMMITTED SNAPSHOT
opzione disattivata).
Attualmente mi affido al fatto che i processi di lettura non possono mai "saltare" un checkpoint. La mia domanda è: posso fare affidamento su questa proprietà? E se no, cosa potrei fare per renderlo vero?
Esempio: quando vengono inserite righe con valori di identità 1, 2, 3, 4 e 5, il lettore non deve vedere la riga con valore 5 prima di vedere quella con valore 4. I test mostrano che la query, che contiene una ORDER BY CheckpointSequence
clausola ( e una WHERE CheckpointSequence > -1
clausola), si blocca in modo affidabile ogni volta che la riga 4 deve essere letta, ma non ancora impegnata, anche se la riga 5 è già stata impegnata.
Credo che almeno in teoria, ci potrebbe essere una condizione di razza qui che potrebbe causare la rottura di questa ipotesi. Sfortunatamente, la documentazione su Identity
non dice molto su come Identity
funziona nel contesto di più transazioni simultanee, dice solo "Ogni nuovo valore viene generato in base al seed e all'incremento correnti". e "Ogni nuovo valore per una determinata transazione è diverso dalle altre transazioni simultanee nella tabella." ( MSDN )
Il mio ragionamento è che deve funzionare in qualche modo così:
- Viene avviata una transazione (esplicitamente o implicitamente).
- Viene generato un valore di identità (X).
- Il blocco di riga corrispondente viene preso sull'indice cluster in base al valore dell'identità (a meno che non entri in azione l'escalation del blocco, nel qual caso l'intera tabella viene bloccata).
- La riga è inserita.
- La transazione viene impegnata (probabilmente un bel po 'di tempo dopo), quindi il blocco viene rimosso di nuovo.
Penso che tra i passaggi 2 e 3, ci sia una finestra molto piccola in cui
- una sessione simultanea potrebbe generare il valore di identità successivo (X + 1) ed eseguire tutti i passaggi rimanenti,
- permettendo così a un lettore che arriva esattamente in quel momento di leggere il valore X + 1, mancando il valore di X.
Naturalmente, la probabilità di ciò sembra estremamente bassa; ma ancora - potrebbe succedere. O potrebbe?
(Se sei interessato al contesto: questa è l'implementazione del motore di persistenza SQL di NEventStore. NEventStore implementa un archivio eventi di sola aggiunta in cui ogni evento ottiene un nuovo numero progressivo di sequenza di checkpoint. I client leggono gli eventi dall'archivio eventi ordinati per checkpoint per eseguire calcoli di ogni tipo. Una volta elaborato un evento con checkpoint X, i clienti considerano solo eventi "più recenti", ovvero eventi con checkpoint X + 1 e successivi. Pertanto, è fondamentale che gli eventi non possano mai essere saltati, dato che non sarebbero mai più stati presi in considerazione. Attualmente sto cercando di determinare se l' Identity
implementazione del checkpoint basata su di essa soddisfa questo requisito. Queste sono le esatte istruzioni SQL utilizzate : schema , query di Writer ,Query del lettore .)
Se ho ragione e potrebbe sorgere la situazione sopra descritta, posso vedere solo due opzioni per gestirli, entrambi insoddisfacenti:
- Quando vedi un valore di sequenza del checkpoint X + 1 prima di aver visto X, ignora X + 1 e riprova più tardi. Tuttavia, poiché
Identity
ovviamente può produrre lacune (ad esempio, quando viene eseguito il rollback della transazione), X potrebbe non arrivare mai. - Quindi, stesso approccio, ma accetta il divario dopo n millisecondi. Tuttavia, quale valore di n dovrei assumere?
Qualche idea migliore?