Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Entendendo o problema de leitura suja com o SQL Server


Um dos problemas mais comuns que ocorrem durante a execução de transações simultâneas é o problema de leitura suja. Uma leitura suja ocorre quando uma transação tem permissão para ler dados que estão sendo modificados por outra transação que está sendo executada simultaneamente, mas que ainda não foi confirmada.

Se a transação que modifica os dados se confirmar, o problema de leitura suja não ocorre. No entanto, se a transação que modifica os dados for revertida após a outra transação ler os dados, a última transação terá dados sujos que na verdade não existem.

Como sempre, certifique-se de ter um backup completo antes de experimentar um novo código. Consulte este artigo sobre como fazer backup de bancos de dados MS SQL se não tiver certeza.

Vamos entender isso com a ajuda de um exemplo. Suponha que tenhamos uma tabela chamada 'Produto' que armazena id, nome e ItemsinStock para o produto.

A tabela fica assim:

[ID da tabela=20 /]

Suponha que você tenha um sistema online onde um usuário possa comprar produtos e visualizar produtos ao mesmo tempo. Dê uma olhada na figura a seguir.

Considere um cenário em que um usuário tenta comprar um produto. A transação 1 realizará a tarefa de compra para o usuário. O primeiro passo na transação será atualizar o ItemsinStock.

Antes da transação, existem 12 itens no estoque; a transação atualizará para 11. A transação agora se comunicará com um gateway de cobrança externo.

Se, neste momento, outra transação, digamos, Transação 2, ler ItemsInStock para laptops, ele lerá 11. No entanto, se posteriormente, o usuário por trás da Transação 1 tiver fundos insuficientes em sua conta, a Transação 1 será lançada voltar e o valor da coluna ItemsInStock será revertido para 12.

No entanto, a Transação 2 tem 11 como valor para a coluna ItemsInStock. Esses são dados sujos e o problema é chamado de problema de leitura suja.

Exemplo de trabalho de problema de leitura suja


Vamos dar uma olhada no problema de leitura suja em ação no SQL Server. Como sempre, primeiro, vamos criar nossa tabela e adicionar alguns dados fictícios a ela. Execute o script a seguir em seu servidor de banco de dados.
CREATE DATABASE pos;

USE pos;

CREATE TABLE products
(
	Id INT PRIMARY KEY,
	Name VARCHAR(50) NOT NULL,
	ItemsinStock INT NOT NULL

)

INSERT into products

VALUES 
(1, 'Laptop', 12),
(2, 'iPhone', 15),
(3, 'Tablets', 10)

Agora, abra duas instâncias do SQL Server Management Studio lado a lado. Executaremos uma transação em cada uma dessas instâncias.

Adicione o script a seguir à primeira instância do SSMS.
USE pos;

SELECT * FROM products

-- Transaction 1

BEGIN Tran

UPDATE products set ItemsInStock = 11
WHERE Id = 1

-- Billing the customer
WaitFor Delay '00:00:10'
Rollback Transaction

No script acima, iniciamos uma nova transação que atualiza o valor da coluna “ItemsInStock” da tabela de produtos onde Id é 1. Em seguida, simulamos o atraso para cobrança do cliente usando as funções ‘WaitFor’ e ‘Delay’. Um atraso de 10 segundos foi definido no script. Depois disso, simplesmente revertemos a transação.

Na segunda instância do SSMS, simplesmente adicionamos a seguinte instrução SELECT.
USE pos;

-- Transaction 2

SELECT * FROM products
WHERE Id = 1

Agora, primeiro execute a primeira transação, ou seja, execute o script na primeira instância do SSMS e, em seguida, execute imediatamente o script na segunda instância do SSMS.

Você verá que ambas as transações continuarão sendo executadas por 10 segundos e depois disso, você verá que o valor da coluna ‘ItemsInStock’ para o registro com Id 1 ainda é 12, conforme mostrado pela segunda transação. Embora a primeira transação a tenha atualizado para 11, esperado por 10 segundos e depois revertida para 12, o valor mostrado pela segunda transação é 12 em vez de 11.

O que realmente aconteceu é que quando executamos a primeira transação, ela atualizou o valor da coluna ‘ItemsinStock’. Em seguida, ele esperou por 10 segundos e, em seguida, reverteu a transação.

Embora tenhamos iniciado a segunda transação imediatamente após a primeira, ela teve que esperar a conclusão da primeira. É por isso que a segunda transação também esperou 10 segundos e porque a segunda transação foi executada imediatamente após a primeira transação concluir sua execução.

Ler o nível de isolamento confirmado


Por que a transação 2 teve que esperar pela conclusão da transação 1 antes de ser executada?

A resposta é que o nível de isolamento padrão entre transações é “leitura confirmada”. O nível de isolamento Read Committed garante que os dados só possam ser lidos por uma transação se estiver no estado confirmado.

Em nosso exemplo, a transação 1 atualizou os dados, mas não os confirmou até que fossem revertidos. É por isso que a transação 2 teve que esperar a transação 1 confirmar os dados ou reverter a transação antes que pudesse ler os dados.

Agora, em cenários práticos, muitas vezes temos várias transações ocorrendo em um único banco de dados ao mesmo tempo e não queremos que cada transação tenha que esperar pela sua vez. Isso pode tornar os bancos de dados muito lentos. Imagine comprar algo online de um grande site que só poderia processar uma transação por vez!

Ler dados não confirmados


A resposta para esse problema é permitir que suas transações funcionem com dados não confirmados.

Para ler dados não confirmados, basta definir o nível de isolamento da transação para “ler não confirmado”. Atualize a transação 2 adicionando um nível de isolamento conforme o script abaixo.
USE pos;

-- Transaction 2
set transaction isolation level read uncommitted

SELECT * FROM products
WHERE Id = 1

Agora, se você executar a transação 1 e imediatamente executar a transação 2, verá que a transação 2 não aguardará que a transação 1 confirme os dados. A transação 2 lerá imediatamente os dados sujos. Isto é mostrado na figura a seguir:

Aqui, a instância à esquerda está executando a transação 1 e a instância à direita está executando a transação 2.

Executamos a transação 1 primeiro, que atualiza o valor de “ItemsinStock” para id 1 a 11 de 12 e, em seguida, aguarda 10 segundos antes de ser revertido.

Enquanto isso, a transação w lê os dados sujos que são 11, conforme mostrado na janela de resultados à direita. Como a transação 1 é revertida, esse não é o valor real na tabela. O valor real é 12. Tente executar a transação 2 novamente e você verá que desta vez ela recupera 12.

Leitura não confirmada é o único nível de isolamento que tem o problema de leitura suja. Esse nível de isolamento é o menos restritivo de todos os níveis de isolamento e permite a leitura de dados não confirmados.

Obviamente, existem prós e contras em usar Read Uncommitted, depende de qual aplicativo seu banco de dados é usado. Obviamente, seria uma péssima ideia usar isso para o banco de dados por trás de sistemas ATM e outros sistemas muito seguros. No entanto, para aplicativos em que a velocidade é muito importante (executando grandes lojas de comércio eletrônico), usar Read Uncommitted faz mais sentido.