Estou tentando resolver um problema com o SQL Server 2008 R2 e estou me agarrando às palhas.
Dado que os dados estão alojados em nós de folha da árvore B e também existem em índices, alguém poderia ler vários valores durante uma única UPDATE
instrução?
Imagine que um processo atualize uma linha:
--Change status from Pending -> Waiting
BEGIN TRANSACTION
UPDATE Transactions SET Status = 'Waiting'
WHERE TransactionID = 12345
COMMIT TRANSACTION
É possível que durante esse BEGIN TRANS; UPDATE; COMMIT
processo outro processo possa ler valores novos e antigos?
Eu me pergunto isso porque a atualização não altera um valor em apenas um lugar. Esse valor existirá em vários lugares:
- nós folha do índice clusterizado
- IX_Transações_1
- IX_Transações_2
- IX_Transações_3
- IX_Transações_4
- IX_Transações_5
- ...
- IX_Transações_n
Se a atualização começar, ela deverá atualizar os valores em todos esses locais. O que acontece se outro processo usar um plano de execução de índice para encontrar o valor:
- IX_Clustered : Aguardando
Pendente - IX_Transactions_1 :
PendenteAguardando - IX_Transactions_2 :
PendenteAguardando - IX_Transactions_3 : Pendente
- IX_Transactions_4 : Pendente
- IX_Transactions_5 : Pendente
- ...
- IX_Transactions_n : Pendente
Nesse instante, se for emitida uma consulta que examine o valor no índice clusterizado, ela encontrará Waiting .
Mas se uma consulta usar IX_Transactions_4
, ela encontrará um valor de Pending
.
Desdocumentando o mecanismo de bloqueio
A ordem de bloqueio internamente não é documentada, mas presumo que o SQL Server tenha um bloqueio compartilhado nos índices clusterizados e não clusterizados:
- IX_Clustered : Compartilhado
- IX_Transactions_1 : Compartilhado
- IX_Transactions_2 : Compartilhado
- IX_Transactions_3 : Compartilhado
- IX_Transactions_4 : Compartilhado
- IX_Transactions_5 : Compartilhado
Em seguida, atualiza esses bloqueios para bloqueios de atualização:
- IX_Clustered : atualização
compartilhada - IX_Transactions_1 : atualização
compartilhada - IX_Transactions_2 : atualização
compartilhada - IX_Transactions_3 : atualização
compartilhada - IX_Transactions_4 : atualização
compartilhada - IX_Transactions_5 : atualização
compartilhada
neste ponto, outro processo ainda pode ler os valores desses 5 índices; porque os selects são compatíveis com os bloqueios de atualização.
Em seguida, a atualização continua. Se tirarmos uma foto em um instante no tempo:
- IX_Clustered :
atualizaçãocompartilhadaexclusiva -> valor alterado - IX_Transactions_1 :
atualizaçãocompartilhadaexclusiva -> valor alterado - IX_Transactions_2 :
atualizaçãocompartilhadaexclusiva - IX_Transactions_3 : atualização
compartilhada - IX_Transactions_4 : atualização
compartilhada - IX_Transactions_5 : atualização
compartilhada
Outro processo não pode mais ler valores de itens com um bloqueio exclusivo , mas ainda pode ler valores de itens que ainda possuem o bloqueio de atualização (os bloqueios compartilhados são compatíveis com os bloqueios de atualização )
A menos é claro que não é isso que acontece
Isso só funciona se o SQL promover apenas para exclusivo conforme necessário. Se, em vez disso, for:
- IX_Clustered :
SUIXX -> valor alterado - IX_Transactions_1 :
SUIXX -> valor alterado - IX_Transactions_2 :
SUIXX - IX_Transactions_3 :
SUIX - IX_Transactions_4 :
SUIX - IX_Transactions_5 :
SUIX
E não consigo mais digitar; minha vontade de continuar é esmagada. Como eu disse, estou me agarrando em palhas.
Você pode usar o sinalizador de rastreamento
1200
para imprimir informações de bloqueio e informações3916
de registro. (Ambos AFAIK não documentados)Para a seguinte configuração de teste
Em seguida, execute o seguinte duas vezes (já que a segunda execução não contém os bloqueios irrelevantes adquiridos durante a compilação)
Isso retorna a seguinte saída
Nenhum bloqueio é feito no índice não clusterizado (
1:166
) até depois daLOP_MODIFY_ROW
atualização na chave do índice clusterizado (na página1:127
), então sim, isso seria possível.E o acima é com um plano de atualização estreito (por linha).
Com um plano amplo (por índice), cada índice é atualizado por vez. Possivelmente com operações de classificação intermediárias, como no plano abaixo.
Isso tornaria a janela de oportunidade mais ampla, mas, enquanto uma transação poderia potencialmente ler o valor "antigo" de um índice ainda a ser atualizado, não seria possível para uma transação confirmada de leitura ler a "nova" versão do valor até a transação foi confirmada.
É certamente possível que uma instrução read commited leia duas versões diferentes do mesmo valor.
Em uma corrida de conexão
E então em outra corrida
Para mim, cerca de 50% das vezes, essa segunda consulta retorna uma linha com
B
valores diferentes conforme o valor muda entre as duas buscas emT
.