No meu banco de dados eu tenho uma Task
tabela. Esta tabela possui vários campos que são chaves estrangeiras para outras tabelas. Existem também algumas tabelas de links, que têm Task
em uma extremidade.
Quando uma nova tarefa é criada (ou uma tarefa antiga é atualizada), preciso ter certeza de que essas chaves estrangeiras existem e retornar um erro significativo se não existirem. Isso é importante porque a chamada se origina de uma API da web, então os consumidores realmente precisam saber por que sua chamada não funcionou, portanto, uma mensagem de erro detalhada (sem fornecer muito) é crucial.
Como há uma dúzia de chaves estrangeiras (e também algumas outras condições, como a conta atual do aplicativo do usuário estar ativa) que tornam a tarefa correta, em vez de tentar inserir e falhar, estou tentando validá-las antes de inserir.
É claro que, quando faço isso, preciso ter certeza de que, quando realmente inserir o registro, essas validações ainda serão verdadeiras.
Portanto, vamos supor que uma tarefa faça referência a um grupo. Da perspectiva da API da Web, o grupo é um dado de referência e retorna uma lista de chaves de grupo válidas em uma chamada anterior. Ele recebe um deles de volta quando cria ou atualiza uma tarefa como um GroupId
campo no Task
objeto.
Para ter certeza de que esta (e outras) chaves estrangeiras estão presentes no banco de dados, abro uma REPEATABLE READ
transação e verifico a existência delas uma a uma antes de tentar inserir a tarefa. Em seguida, compilo uma lista daqueles que estão ausentes (se houver) e reverto a transação e os devolvo para que a API possa formar uma mensagem de erro significativa.
A questão é se REPEATABLE READ
o nível de isolamento é adequado neste caso. Paul White mencionou que muitas vezes não é .
REPEATABLE READ
é provavelmente o nível de isolamento correto a ser usado neste caso, assumindo que os dados que você está verificando não mudam com muita frequência. Esse nível de isolamento garante que as linhas realmente lidas anteriormente na transação continuarão a existir, inalteradas, até que a transação seja concluída, obtendo e mantendo bloqueios compartilhados.Uma transação executada neste nível de isolamento pode perder linhas que existiam no início da transação, mas que se moveram durante a transação de tal forma que se moveram da frente para trás da busca ou varredura tentando localizá-las. Ele também pode encontrar novas linhas em uma segunda execução da mesma consulta que não existia no início (fantasmas).
Portanto, em casos raros, sua transação pode falhar ao aprovar uma inserção que seria bem-sucedida se tentada. Isso não resultaria em falha subsequente de uma inserção validada, o que provavelmente é sua principal preocupação.
Se você precisar de garantias de isolamento mais completas, considere
SERIALIZABLE
- mas certifique-se de que os índices adequados existam para oferecer suporte a bloqueios mínimos de intervalo de chaves.Usar
SNAPSHOT
o isolamento (como mencionei em sua pergunta anterior) forneceria uma visão consistente dos dados (como eles apareceram no momento em que a transação foi iniciada), mas você pode encontrar um conflito de atualização ao tentar inserir a entrada 'validada', se houver modificações simultâneas resultam na falha das verificações de chave estrangeira. Isso não forneceria o nível de detalhes necessário para uma mensagem de erro significativa, portanto,SNAPSHOT
não é a escolha certa aqui.Para mais informações, veja:
O nível de isolamento de leitura repetível
O nível de isolamento serializável
As propriedades ACID de instruções e transações