TL; DR: A pergunta abaixo se resume a: Ao inserir uma linha, existe uma janela de oportunidade entre a geração de um novo Identity
valor e o bloqueio da chave de linha correspondente no índice em cluster, onde um observador externo pode ver um novo Identity
valor inserido por uma transação simultânea? (No SQL Server.)
Versão detalhada
Eu tenho uma tabela do SQL Server com uma Identity
coluna chamada CheckpointSequence
, que é a chave do índice clusterizado da tabela (que também possui vários índices adicionais não clusterizados). As linhas são inseridas na tabela por vários processos e encadeamentos simultâneos (no nível de isolamento READ COMMITTED
e sem IDENTITY_INSERT
). Ao mesmo tempo, há processos que lêem periodicamente as linhas do índice em cluster, ordenadas por essa CheckpointSequence
coluna (também no nível de isolamento READ COMMITTED
, com a READ COMMITTED SNAPSHOT
opção desativada).
Atualmente, confio no fato de que os processos de leitura nunca podem "pular" um ponto de verificação. Minha pergunta é: Posso confiar nessa propriedade? E se não, o que eu poderia fazer para torná-lo realidade?
Exemplo: quando linhas com os valores de identidade 1, 2, 3, 4 e 5 são inseridas, o leitor não deve ver a linha com o valor 5 antes de ver a com o valor 4. Os testes mostram que a consulta, que contém uma ORDER BY CheckpointSequence
cláusula ( e uma WHERE CheckpointSequence > -1
cláusula), bloqueia de forma confiável sempre que a linha 4 for lida, mas ainda não confirmada, mesmo que a linha 5 já tenha sido confirmada.
Acredito que, pelo menos em teoria, pode haver uma condição de corrida aqui que possa causar essa suposição. Infelizmente, a documentação Identity
não diz muito sobre como Identity
funciona no contexto de várias transações simultâneas, apenas diz "Cada novo valor é gerado com base na atual semente e incremento". e "Cada novo valor para uma transação específica é diferente de outras transações simultâneas na tabela". ( MSDN )
Meu raciocínio é que ele deve funcionar de alguma forma assim:
- Uma transação é iniciada (explícita ou implicitamente).
- Um valor de identidade (X) é gerado.
- O bloqueio de linha correspondente é obtido no índice clusterizado com base no valor da identidade (a menos que a escalação do bloqueio seja iniciada, nesse caso, a tabela inteira está bloqueada).
- A linha é inserida.
- A transação é confirmada (possivelmente muito tempo depois), portanto, o bloqueio é removido novamente.
Eu acho que entre os passos 2 e 3, há uma janela muito pequena onde
- uma sessão simultânea pode gerar o próximo valor de identidade (X + 1) e executar todas as etapas restantes,
- permitindo assim que um leitor que esteja exatamente nesse ponto do tempo leia o valor X + 1, perdendo o valor de X.
Obviamente, a probabilidade disso parece extremamente baixa; mas ainda assim - isso poderia acontecer. Ou poderia?
(Se você estiver interessado no contexto: Esta é a implementação do SQL Persistence Engine da NEventStore. O NEventStore implementa um armazenamento de eventos somente para acréscimos, onde cada evento recebe um novo número de sequência de ponto de verificação crescente. para realizar cálculos de todos os tipos. Depois que um evento com ponto de verificação X for processado, os clientes consideram apenas eventos "mais recentes", ou seja, eventos com ponto de verificação X + 1 e acima. Portanto, é vital que os eventos nunca possam ser ignorados, como eles nunca seriam considerados novamente. No momento, estou tentando determinar se a Identity
implementação do ponto de verificação com base em conformidade com esse requisito. Estas são as instruções SQL exatas usadas : Esquema , consulta do Writer ,Consulta do leitor .)
Se eu estiver certo e a situação descrita acima puder surgir, posso ver apenas duas opções para lidar com elas, ambas insatisfatórias:
- Ao visualizar um valor de sequência de ponto de verificação X + 1 antes de ver X, descarte X + 1 e tente novamente mais tarde. No entanto, como
Identity
é claro que pode produzir lacunas (por exemplo, quando a transação é revertida), X pode nunca chegar. - Portanto, mesma abordagem, mas aceite a diferença após n milissegundos. No entanto, que valor de n devo assumir?
Alguma ideia melhor?