TL; DR: La siguiente pregunta se reduce a: Al insertar una fila, ¿hay una ventana de oportunidad entre la generación de un nuevo Identity
valor y el bloqueo de la clave de fila correspondiente en el índice agrupado, donde un observador externo podría ver un nuevo Identity
valor insertado por una transacción concurrente? (En SQL Server).
Versión detallada
Tengo una tabla de SQL Server con una Identity
columna llamada CheckpointSequence
, que es la clave del índice agrupado de la tabla (que también tiene varios índices no agrupados adicionales). Las filas se insertan en la tabla mediante varios procesos y subprocesos concurrentes (a nivel de aislamiento READ COMMITTED
y sin él IDENTITY_INSERT
). Al mismo tiempo, hay procesos que leen periódicamente filas del índice agrupado, ordenadas por esa CheckpointSequence
columna (también a nivel de aislamiento READ COMMITTED
, con la READ COMMITTED SNAPSHOT
opción desactivada).
Actualmente confío en el hecho de que los procesos de lectura nunca pueden "omitir" un punto de control. Mi pregunta es: ¿Puedo confiar en esta propiedad? Y si no, ¿qué podría hacer para que sea verdad?
Ejemplo: cuando se insertan filas con valores de identidad 1, 2, 3, 4 y 5, el lector no debe ver la fila con el valor 5 antes de ver la que tiene el valor 4. Las pruebas muestran que la consulta, que contiene una ORDER BY CheckpointSequence
cláusula ( y una WHERE CheckpointSequence > -1
cláusula), bloquea de manera confiable cada vez que se lee la fila 4, pero aún no se ha confirmado, incluso si la fila 5 ya se ha confirmado.
Creo que, al menos en teoría, puede haber una condición de carrera aquí que podría hacer que esta suposición se rompa. Desafortunadamente, la documentación sobre Identity
no dice mucho acerca de cómo Identity
funciona en el contexto de múltiples transacciones concurrentes, solo dice "Cada nuevo valor se genera en función del valor inicial y el incremento actual". y "Cada nuevo valor para una transacción particular es diferente de otras transacciones concurrentes en la tabla". ( MSDN )
Mi razonamiento es que debe funcionar de alguna manera así:
- Se inicia una transacción (explícita o implícitamente).
- Se genera un valor de identidad (X).
- El bloqueo de fila correspondiente se toma en el índice agrupado en función del valor de identidad (a menos que se active la escalada de bloqueo, en cuyo caso toda la tabla está bloqueada).
- Se inserta la fila.
- La transacción se confirma (posiblemente mucho tiempo después), por lo que el bloqueo se elimina nuevamente.
Creo que entre los pasos 2 y 3, hay una ventana muy pequeña donde
- una sesión concurrente podría generar el siguiente valor de identidad (X + 1) y ejecutar todos los pasos restantes,
- permitiendo así que un lector que llegue exactamente en ese punto de tiempo lea el valor X + 1, sin el valor de X.
Por supuesto, la probabilidad de esto parece extremadamente baja; pero aún así, podría suceder. O podría?
(Si está interesado en el contexto: esta es la implementación del Motor de persistencia SQL de NEventStore. NEventStore implementa un almacén de eventos de solo agregado donde cada evento obtiene un nuevo número de secuencia de punto de control ascendente. Los clientes leen los eventos del almacén de eventos ordenados por punto de control para realizar cálculos de todo tipo. Una vez que se ha procesado un evento con punto de control X, los clientes solo consideran eventos "más nuevos", es decir, eventos con punto de control X + 1 y superior. Por lo tanto, es vital que los eventos nunca se puedan omitir, ya que nunca se volverían a considerar. Actualmente estoy tratando de determinar si la Identity
implementación del punto de control basado en los requisitos cumple con este requisito. Estas son las sentencias SQL exactas utilizadas : esquema , consulta del escritor ,Consulta del lector .)
Si estoy en lo cierto y podría surgir la situación descrita anteriormente, solo puedo ver dos opciones para tratar con ellos, los cuales son insatisfactorios:
- Cuando vea un valor de secuencia de punto de control X + 1 antes de haber visto X, descarte X + 1 e intente nuevamente más tarde. Sin embargo, debido a que, por
Identity
supuesto , puede producir brechas (p. Ej., Cuando se revierte la transacción), es posible que X nunca aparezca. - Entonces, el mismo enfoque, pero acepta la brecha después de n milisegundos. Sin embargo, ¿qué valor de n debo asumir?
¿Alguna idea mejor?