Database
 sql >> Base de Dados >  >> RDS >> Database

O nível de isolamento não confirmado de leitura

[ Veja o índice para toda a série ]

Read uncommitted é o mais fraco dos quatro níveis de isolamento de transação definidos no SQL Standard (e dos seis implementados no SQL Server). Ele permite todos os três chamados "fenômenos de simultaneidade", leituras sujas , leituras não repetíveis , e fantasmas:



A maioria das pessoas de banco de dados está ciente desses fenômenos, pelo menos em linhas gerais, mas nem todos percebem que eles não descrevem completamente as garantias de isolamento oferecidas; nem descrevem intuitivamente os diferentes comportamentos que se pode esperar em uma implementação específica como o SQL Server. Mais sobre isso mais tarde.

Isolamento de transação - o 'I' em ACID


Cada comando SQL é executado em uma transação (explícita, implícita ou confirmação automática). Cada transação tem um nível de isolamento associado, que determina o quão isolada ela está dos efeitos de outras transações simultâneas. Esse conceito um tanto técnico tem implicações importantes para a forma como as consultas são executadas e a qualidade dos resultados que elas produzem.

Considere uma consulta simples que conta todas as linhas em uma tabela. Se essa consulta pudesse ser executada instantaneamente (ou com zero modificações de dados simultâneas), poderia haver apenas uma resposta correta:o número de linhas fisicamente presentes na tabela naquele momento. Na realidade, a execução da consulta levará um certo tempo e o resultado dependerá de quantas linhas o mecanismo de execução realmente encontrar ao percorrer qualquer estrutura física escolhida para acessar os dados.

Se as linhas estiverem sendo adicionadas (ou excluídas) à tabela por transações simultâneas enquanto a operação de contagem estiver em andamento, resultados diferentes poderão ser obtidos dependendo se a transação de contagem de linhas encontrar todas, algumas ou nenhuma dessas alterações simultâneas – o que por sua vez, depende do nível de isolamento da transação de contagem de linhas.

Dependendo do nível de isolamento, detalhes físicos e tempo das operações simultâneas, nossa transação de contagem pode até produzir um resultado que nunca foi um reflexo verdadeiro do estado confirmado da tabela em qualquer ponto do tempo durante a transação.

Exemplo


Considere uma transação de contagem de linhas que começa no tempo T1 e varre a tabela do início ao fim (na ordem de chave de índice clusterizado, para fins de argumento). Nesse momento, existem 100 linhas confirmadas na tabela. Algum tempo depois (no tempo T2), nossa transação de contagem encontrou 50 dessas linhas. Ao mesmo tempo, uma transação concorrente insere duas linhas na tabela e confirma um pouco mais tarde no tempo T3 (antes que a transação de contagem termine). Uma das linhas inseridas está dentro da metade da estrutura de índice clusterizado que nossa transação de contagem já processou, enquanto a outra linha inserida fica na parte não contada.

Quando a transação de contagem de linhas for concluída, ela relatará 101 linhas neste cenário; 100 linhas inicialmente na tabela mais a única linha inserida que foi encontrada durante a varredura. Esse resultado está em desacordo com o histórico de commits da tabela:havia 100 linhas confirmadas nos momentos T1 e T2, então 102 linhas confirmadas no momento T3. Nunca houve um momento em que havia 101 linhas confirmadas.

A coisa surpreendente (talvez, dependendo de quão profundamente você pensou sobre essas coisas antes) é que esse resultado é possível no nível de isolamento de leitura confirmada padrão (bloqueio) e mesmo sob isolamento de leitura repetível. Ambos os níveis de isolamento são garantidos para ler apenas dados confirmados, mas obtivemos um resultado que representa nenhum estado confirmado do banco de dados!

Análise


O único nível de isolamento de transação que fornece isolamento completo dos efeitos de simultaneidade é serializável. A implementação do SQL Server do nível de isolamento serializável significa que uma transação verá os dados confirmados mais recentes, a partir do momento em que os dados foram bloqueados para acesso. Além disso, é garantido que o conjunto de dados encontrado sob isolamento serializável não alterará sua associação antes que a transação termine.

O exemplo de contagem de linhas destaca um aspecto fundamental da teoria do banco de dados:precisamos ser claros sobre o que um resultado "correto" significa para um banco de dados que sofre modificações simultâneas e precisamos entender as trocas que estamos fazendo ao selecionar um isolamento nível inferior ao serializável.

Se precisarmos de uma visão pontual do estado confirmado do banco de dados, devemos usar o isolamento de instantâneo (para garantias em nível de transação) ou ler o isolamento de instantâneo confirmado (para garantias em nível de instrução). Observe que uma visualização pontual significa que não estamos necessariamente operando no estado atual de commit do banco de dados; com efeito, podemos estar usando informações desatualizadas. Por outro lado, se estivermos satisfeitos com os resultados baseados apenas em dados confirmados (embora possivelmente de diferentes pontos no tempo), podemos optar por manter o nível de isolamento confirmado de leitura de bloqueio padrão.

Para ter certeza de produzir resultados (e tomar decisões!) com base no último conjunto de dados confirmados, para algum histórico serial de operações no banco de dados, precisaríamos de isolamento de transação serializável. É claro que essa opção é normalmente a mais cara em termos de uso de recursos e menor simultaneidade (incluindo um risco elevado de impasses).

No exemplo de contagem de linhas, ambos os níveis de isolamento de instantâneo (SI e RCSI) dariam um resultado de 100 linhas, representando a contagem de linhas confirmadas no início da instrução (e transação neste caso). A execução da consulta no bloqueio de leitura confirmada ou isolamento de leitura repetível pode produzir um resultado de 100, 101 ou 102 linhas – dependendo do tempo, da granularidade do bloqueio, da posição de inserção da linha e do método de acesso físico escolhido. Sob isolamento serializável, o resultado seria 100 ou 102 linhas, dependendo de qual das duas transações simultâneas é considerada executada primeiro.

Quão ruim é a leitura não confirmada?


Tendo introduzido o isolamento de leitura não confirmada como o mais fraco dos níveis de isolamento disponíveis, você deve esperar que ele ofereça garantias de isolamento ainda menores do que o bloqueio de leitura confirmada (o próximo nível de isolamento mais alto). De fato, sim; mas a questão é:quão pior do que bloquear o isolamento comprometido de leitura é?

Para começarmos com o contexto correto, aqui está uma lista dos principais efeitos de simultaneidade que podem ser experimentados no nível de isolamento de leitura confirmada de bloqueio padrão do SQL Server:
  • Linhas confirmadas ausentes
  • Linhas encontradas várias vezes
  • Diferentes versões da mesma linha encontradas em uma única instrução/plano de consulta
  • Dados de coluna confirmados de diferentes pontos no tempo na mesma linha (exemplo)

Esses efeitos de simultaneidade são todos devidos à implementação de bloqueio de leitura confirmada apenas com bloqueios compartilhados de muito curto prazo ao ler dados. O nível de isolamento de leitura não confirmada vai um passo além, não aceitando bloqueios compartilhados, resultando na possibilidade adicional de "leituras sujas".

Leituras sujas


Como um lembrete rápido, uma "leitura suja" refere-se à leitura de dados que estão sendo alterados por outra transação simultânea (onde "alterar" incorpora operações de inserção, atualização, exclusão e mesclagem). Dito de outra forma, uma leitura suja ocorre quando uma transação lê dados que outra transação modificou, antes que a transação modificadora tenha confirmado ou abortado essas alterações.

Vantagens e Desvantagens


As principais vantagens do isolamento de leitura não confirmada são o potencial reduzido de bloqueio e impasse devido a bloqueios incompatíveis (incluindo bloqueio desnecessário devido ao escalonamento de bloqueio) e possivelmente aumento de desempenho (evitando a necessidade de adquirir e liberar bloqueios compartilhados).

A desvantagem potencial mais óbvia de ler o isolamento não confirmado é (como o nome sugere) que podemos ler dados não confirmados (mesmo dados que nunca confirmado, no caso de um rollback de transação). Em um banco de dados onde os rollbacks são relativamente raros, a questão da leitura de dados não confirmados pode ser vista como um mero problema de tempo, já que os dados em questão certamente serão confirmados em algum momento e provavelmente em breve. Já vimos inconsistências relacionadas ao tempo no exemplo de contagem de linhas (que estava operando em um nível de isolamento mais alto), portanto, pode-se questionar o quanto é preocupante ler dados "muito cedo".

Claramente, a resposta depende das prioridades e do contexto local, mas uma decisão informada de usar o isolamento não comprometido de leitura certamente parece possível. No entanto, há mais em que pensar. A implementação do SQL Server do nível de isolamento não confirmado de leitura inclui alguns comportamentos sutis dos quais precisamos estar cientes antes de fazer essa "escolha informada".

Verificações de pedidos de alocação


O uso do isolamento de leitura não confirmada é considerado pelo SQL Server como um sinal de que estamos preparados para aceitar as inconsistências que podem surgir como resultado de uma verificação ordenada por alocação.

Normalmente, o mecanismo de armazenamento só pode escolher uma varredura ordenada por alocação se os dados subjacentes forem garantidos a não serem alterados durante a varredura (porque, por exemplo, o banco de dados é somente leitura ou uma dica de bloqueio de tabela foi especificada). No entanto, quando o isolamento de leitura não confirmada está em uso, o mecanismo de armazenamento ainda pode escolher uma varredura ordenada por alocação, mesmo quando os dados subjacentes podem ser modificados por transações simultâneas.

Nessas circunstâncias, a varredura ordenada por alocação pode perder completamente alguns dados confirmados ou encontrar outros dados confirmados mais de uma vez. A ênfase está na falta ou contagem dupla de comprometidos data (não lendo dados não confirmados), portanto, não é um caso de "leituras sujas" como tal. Essa decisão de design (para permitir varreduras ordenadas por alocação sob isolamento de leitura não confirmada) é vista por algumas pessoas como bastante controversa.

Como ressalva, devo deixar claro que o risco mais geral de perder ou contar duas linhas confirmadas não se limita a ler o isolamento não confirmado. Certamente é possível ver efeitos semelhantes no bloqueio de leitura confirmada e leitura repetível (como vimos anteriormente), mas isso ocorre por meio de um mecanismo diferente. Linhas confirmadas ausentes ou encontrá-las várias vezes devido a uma verificação ordenada por alocação sobre dados alterados é específico para usar o isolamento não confirmado de leitura.

Ler dados "corruptos"


Resultados que parecem desafiar a lógica (e até mesmo verificar restrições!) são possíveis sob o bloqueio de isolamento de leitura confirmada (novamente, veja este artigo de Craig Freedman para alguns exemplos). Para resumir, o ponto é que o bloqueio de leitura confirmada pode ver dados confirmados de diferentes pontos no tempo – mesmo para uma única linha se, por exemplo, o plano de consulta usar técnicas como interseção de índice.

Esses resultados podem ser inesperados, mas estão completamente alinhados com a garantia de ler apenas dados confirmados. Não há como fugir do fato de que garantias de consistência de dados mais altas exigem níveis de isolamento mais altos.

Esses exemplos podem até ser bastante chocantes, se você não os viu antes. Os mesmos resultados são possíveis sob o isolamento de leitura não confirmada, é claro, mas permitir leituras sujas adiciona uma dimensão extra:os resultados podem incluir dados confirmados e não confirmados de diferentes pontos no tempo, mesmo para a mesma linha.

Indo além, é possível que uma transação de leitura não confirmada leia um valor de coluna única em um estado misto de dados confirmados e não confirmados. Isso pode ocorrer ao ler um valor LOB (por exemplo, xml ou qualquer um dos tipos 'max') se o valor for armazenado em várias páginas de dados. Uma leitura não confirmada pode encontrar dados confirmados ou não confirmados de diferentes pontos no tempo em páginas diferentes, resultando em um valor final de coluna única que é uma mistura de valores!

Para dar um exemplo, considere uma única coluna varchar(max) que contém inicialmente 10.000 caracteres 'x'. Uma transação simultânea atualiza esse valor para 10.000 caracteres 'y'. Uma transação de leitura não confirmada pode ler caracteres 'x' de uma página do LOB e caracteres 'y' de outra, resultando em um valor de leitura final contendo uma mistura de caracteres 'x' e 'y'. É difícil argumentar que isso não representa a leitura de dados "corruptos".

Demonstração


Crie uma tabela clusterizada com uma única linha de dados LOB:
CREATE TABLE dbo.Test
(
    RowID integer PRIMARY KEY,
    LOB varchar(max) NOT NULL,
);
 
INSERT dbo.Test
    (RowID, LOB)
VALUES
    (1, REPLICATE(CONVERT(varchar(max), 'X'), 16100));

Em uma sessão separada, execute o script a seguir para ler o valor LOB na leitura do isolamento não confirmado:
-- Run this in session 2
SET NOCOUNT ON;
 
DECLARE 
    @ValueRead varchar(max) = '',
    @AllXs varchar(max) = REPLICATE(CONVERT(varchar(max), 'X'), 16100),
    @AllYs varchar(max) = REPLICATE(CONVERT(varchar(max), 'Y'), 16100);
 
WHILE 1 = 1
BEGIN
    SELECT @ValueRead = T.LOB
    FROM dbo.Test AS T WITH (READUNCOMMITTED)
    WHERE T.RowID = 1;
 
    IF @ValueRead NOT IN (@AllXs, @AllYs)
    BEGIN
    	PRINT LEFT(@ValueRead, 8000);
        PRINT RIGHT(@ValueRead, 8000);
        BREAK;
    END
END;

Na primeira sessão, execute este script para gravar valores alternados na coluna LOB:
-- Run this in session 1
SET NOCOUNT ON;
 
DECLARE 
    @AllXs varchar(max) = REPLICATE(CONVERT(varchar(max), 'X'), 16100),
    @AllYs varchar(max) = REPLICATE(CONVERT(varchar(max), 'Y'), 16100);
 
WHILE 1 = 1
BEGIN
    UPDATE dbo.Test
    SET LOB = @AllYs
    WHERE RowID = 1;
 
    UPDATE dbo.Test
    SET LOB = @AllXs
    WHERE RowID = 1;
END;

Após um curto período de tempo, o script na sessão dois será encerrado, tendo lido um estado misto para o valor LOB, por exemplo:



Esse problema específico está limitado a leituras de valores de coluna LOB que estão espalhados por várias páginas, não devido a quaisquer garantias fornecidas pelo nível de isolamento, mas porque o SQL Server usa travas de nível de página para garantir a integridade física. Um efeito colateral desse detalhe de implementação é que ele evita tais leituras de dados "corrompidas" se os dados de uma única operação de leitura residirem em uma única página.

Dependendo da versão do SQL Server que você possui, se os dados de "estado misto" forem lidos para uma coluna xml, você receberá um erro resultante do resultado xml possivelmente malformado, nenhum erro ou o erro específico não confirmado 601 , "não foi possível continuar a varredura com NOLOCK devido à movimentação de dados." A leitura de dados de estado misto para outros tipos de LOB geralmente não resulta em uma mensagem de erro; o aplicativo ou consulta de consumo não tem como saber que acabou de passar pelo pior tipo de leitura suja. Para concluir a análise, uma linha de estado misto não LOB lida como resultado de uma interseção de índice nunca é relatada como um erro.

A mensagem aqui é que, se você usar o isolamento de leitura não confirmada, você aceita que as leituras sujas incluam a possibilidade de ler valores LOB de estado misto "corrompidos".

A dica NOLOCK


Suponho que nenhuma discussão sobre o nível de isolamento não confirmado de leitura seria completa sem pelo menos mencionar essa dica de tabela (amplamente usada e incompreendida). A dica em si é apenas um sinônimo da dica de tabela READUNCOMMITTED. Ele executa exatamente a mesma função:o objeto ao qual é aplicado é acessado usando a semântica de isolamento de leitura não confirmada (embora haja uma exceção).

No que diz respeito ao nome "NOLOCK", significa simplesmente que nenhum bloqueio compartilhado é feito ao ler dados . Outros bloqueios (estabilidade de esquema, bloqueios exclusivos para modificação de dados e assim por diante) ainda são considerados normais.

De um modo geral, as dicas NOLOCK devem ser tão comuns quanto outras dicas de tabela de nível de isolamento por objeto, como SERIALIZABLE e READCOMMITTEDLOCK. Ou seja:não muito comum, e usado apenas onde não há uma boa alternativa, um propósito bem definido e um completo compreensão das consequências.

Um exemplo de uso legítimo de NOLOCK (ou READUNCOMMITTED) é ao acessar DMVs ou outras visualizações do sistema, onde um nível de isolamento mais alto pode causar contenção indesejada em estruturas de dados que não são do usuário. Outro exemplo de caso extremo pode ser quando uma consulta precisa acessar uma parte significativa de uma tabela grande, que garante nunca sofrer alterações de dados enquanto a consulta sugerida está em execução. Seria necessário haver uma boa razão para não usar o instantâneo ou ler o isolamento de instantâneo confirmado, e os aumentos de desempenho esperados precisariam ser testados, validados e comparados com, digamos, o uso de uma única dica de bloqueio de tabela compartilhada.

O uso menos desejável do NOLOCK é o que infelizmente é o mais comum:aplicá-lo a cada objeto em uma consulta como uma espécie de interruptor mágico mais rápido. Com a melhor vontade do mundo, não há melhor maneira de fazer o código do SQL Server parecer decididamente amador. Se você precisa legitimamente ler o isolamento não confirmado para uma consulta, bloco de código ou módulo, provavelmente é melhor definir o nível de isolamento da sessão adequadamente e fornecer comentários para justificar a ação.

Considerações finais


A leitura não confirmada é uma escolha legítima para o nível de isolamento da transação, mas precisa ser uma escolha informada. Como lembrete, aqui estão alguns dos fenômenos de simultaneidade possíveis sob o isolamento de confirmação de leitura de bloqueio padrão do SQL Server:
  • Linhas confirmadas anteriormente ausentes
  • Linhas confirmadas encontradas várias vezes
  • Diferentes versões confirmadas da mesma linha encontradas em uma única instrução/plano de consulta
  • Dados confirmados de diferentes pontos no tempo na mesma linha (mas em colunas diferentes)
  • Leituras de dados confirmadas que parecem contradizer restrições ativadas e verificadas

Dependendo do seu ponto de vista, essa pode ser uma lista bastante chocante de possíveis inconsistências para o nível de isolamento padrão. Para essa lista, leia o isolamento não confirmado:
  • Leituras sujas (encontrando dados que ainda não foram confirmados e podem nunca ser confirmados)
  • Linhas contendo uma mistura de dados confirmados e não confirmados
  • Linhas perdidas/duplicadas devido a verificações ordenadas por alocação
  • Valores LOB individuais (coluna única) de estado misto ("corrompido")
  • Erro 601 – "não foi possível continuar a verificação com NOLOCK devido à movimentação de dados" (exemplo).

Se suas principais preocupações transacionais são sobre os efeitos colaterais do bloqueio de isolamento de leitura confirmada – bloqueio, sobrecarga de bloqueio, simultaneidade reduzida devido ao escalonamento de bloqueio e assim por diante – você pode ser melhor atendido por um nível de isolamento de versão de linha, como isolamento de instantâneo de leitura confirmada (RCSI) ou isolamento de instantâneo (SI). No entanto, eles não são gratuitos e as atualizações do RCSI em particular têm alguns comportamentos contra-intuitivos.

Para cenários que exigem os mais altos níveis de garantias de consistência, a serialização continua sendo a única opção segura. Para operações críticas de desempenho em dados somente leitura (por exemplo, grandes bancos de dados que são efetivamente somente leitura entre janelas ETL), definir explicitamente o banco de dados como READ_ONLY também pode ser uma boa opção (bloqueios compartilhados não são usados ​​quando o banco de dados é somente leitura e não há risco de inconsistência).

Haverá também um número relativamente pequeno de aplicativos para os quais o isolamento de leitura não confirmada é a escolha certa. Esses aplicativos precisam estar satisfeitos com resultados aproximados e com a possibilidade de dados ocasionalmente inconsistentes, aparentemente inválidos (em termos de restrições) ou "provavelmente corrompidos". Se os dados forem alterados com pouca frequência, o risco dessas inconsistências também será menor.
[ Veja o índice para toda a série ]