Problemas com NOLOCK (SQL Server)

Estava comentando sobre a recomendação indiscriminada do uso do NOLOCK, mas até que ponto isso é bom ou ruim. Se a vantagem em performance é conhecida, quais seriam as desvantagens?

Links relacionados

O primeiro ponto é que NOLOCK funciona somente para as operações de LEITURA e nunca podem ser combinados com a escrita, exceto pelos casos descritos no post NOLOCK e INSERT/UPDATE/DELETE.

A grande desvantagem do uso de NOLOCK é o fato de permitir que o banco de dados realize uma LEITURA SUJA. O que seria a tal leitura suja? Vamos descrever por exemplos.

 

Exemplo 1

Toda transação apresenta um comportamento atômico, ou seja, tudo ou nada. A transação abaixo remove os registros da tabela tbUsuarios e tbPedidos simultaneamente, na qual os registros são apagados de ambas as tabelas ou de nenhuma tabela. Não existe um estado transitório – no qual alguns registros ‘fabricio’ se encontram na tabela tbPedidos, mas já foram apagados de tbUsuarios. Isso se chama consistência de dados.

BEGIN TRANSACTION

DELETE tbUsuarios WHERE nome = 'fabricio'

DELETE tbPedidos WHERE usuario = 'fabricio'

COMMIT TRANSACTION

A garantia de consistência de dados é realizada através dos bloqueios (LOCK), que são mantidos até a fase de COMMIT TRANSACTION. Somente após finalizar a transação, os LOCKs são liberados e a leitura é permitida. Isso corresponde a uma leitura consistente (READ COMMITTED).

Por outro lado, ao usar um comando com NOLOCK, estamos sinalizando ao SQL Server que utilize uma leitura suja (READ UNCOMMITTED). Essa leitura não espera pelo final da transação, ou seja, a leitura é observa todos os estados transitórios.

 

Exemplo 2

Ainda pensando na consistência de dados usando transação, imagine a situação na qual temos a seguinte operação.

BEGIN TRANSACTION

-- Marca o registro como temporário

UPDATE tbUsuarios SET nome = 'tmp' WHERE nome = 'fabricio'

-- Defaz a operação

UPDATE tbUsuarios SET nome = 'fabricio' WHERE nome = 'tmp'

COMMIT TRANSACTION

A transação efetua a modificação de FABRICIO –> TMP –> FABRICIO, ou seja, não faz absolutamente nenhuma mudança comparando os estados inicial e final. Isso garante que o simples comando abaixo sempre retorne o nome correto.

SELECT * FROM tbUsuarios

Se fosse utilizado o comando NOLOCK, existiria a remota possibilidade de observarmos os estados transitórios.

SELECT * FROM tbUsuarios WITH (NOLOCK)

Nesse caso, a consulta poderia retornar o valor TMP.

 

Exemplo 3

Nos exemplos 1 e 2, falamos exaustivamente sobre o comportamento do NOLOCK retornar informações transitórias. Esse exemplo 3 é um passo adiante, algo um pouco mais avançado, no qual a natureza transitória pode causar erros (!!!). O post Efeitos colaterais do NOLOCK – Parte 1 apresenta um script para mostrar um exemplo do erro 601.

Msg 601, Level 12, State 3, Line 1
Could not continue scan with NOLOCK due to data movement.

O que aconteceu aqui foi exatamente o comportamento do exemplo 1 – enquanto o banco de dados efetuava o JOIN entre tabelas, o registro desapareceu de forma inesperada. Isso gerou o erro 601 de severidade 12. Uma forma de corrigir é utilizando bloqueios … ou melhor, evitando o uso de NOLOCK.

Apenas por curiosidade, vale a pena dar uma olhada em uma outra particularidade (não tão comum) do NOLOCK em Efeitos colaterais do NOLOCK – Parte 2.

 

Recomendação

Sim, ainda recomendo que utilize NOLOCK para garantir performance no banco de dados. Mas não se esqueça das leituras sujas e dos erros transitórios.

A partir do SQL Server 2005, existe uma funcionalidade chamada READ COMMITTED SNAPSHOT. Esse recurso elimina (praticamente 100%) a necessidade de usar NOLOCK. A melhor parte é que não é necessário mudar o código da aplicação, basta ativá-lo na configuração do banco de dados.