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

O problema de atualização perdida em transações simultâneas


O problema de atualização perdida ocorre quando 2 transações simultâneas tentam ler e atualizar os mesmos dados. Vamos entender isso com a ajuda de um exemplo.

Suponha que temos uma tabela chamada “Product” que armazena id, name e ItemsinStock para um produto.

Ele é usado como parte de um sistema online que exibe o número de itens em estoque para um determinado produto e, portanto, precisa ser atualizado toda vez que uma venda desse produto é realizada.



A tabela fica assim:

Id

Nome

Itens em Estoque

1

Laptops

12


Agora considere um cenário em que um usuário chega e inicia o processo de compra de um laptop. Isso iniciará uma transação. Vamos chamar essa transação de transação 1.

Ao mesmo tempo que outro usuário faz login no sistema e inicia uma transação, vamos chamar essa transação de 2. Observe a figura a seguir.



A transação 1 lê os itens em estoque para laptops, que é 12. Um pouco mais tarde, a transação 2 lê o valor de ItemsinStock para laptops que ainda será 12 neste momento. A transação 2 vende três laptops, pouco antes da transação 1 vender 2 itens.

A transação 2 concluirá sua execução primeiro e atualizará o ItemsinStock para 9, pois vendeu três dos 12 laptops. A transação 1 se compromete. Como a transação 1 vendeu dois itens, ela atualiza ItemsinStock para 10.

Isso está incorreto, o número correto é 12-3-2 =7

Exemplo de trabalho de problema de atualização perdida

Vamos dar uma olhada no problema de atualização perdida em ação no SQL Server. Como sempre, primeiro, vamos criar uma tabela e adicionar alguns dados fictícios nela.

Como sempre, certifique-se de fazer o backup adequado antes de jogar com o novo código. Se você não tiver certeza, consulte este artigo sobre backup do SQL Server.

Execute o script a seguir em seu servidor de banco de dados.
<span style="font-size: 14px;">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, 'Iphon', 15),
(3, 'Tablets', 10)</span>

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.
<span style="font-size: 14px;">USE pos;

-- Transaction 1

BEGIN TRAN

DECLARE @ItemsInStock INT

SELECT @ItemsInStock = ItemsInStock
FROM products WHERE Id = 1

WaitFor Delay '00:00:12'
SET @ItemsInStock = @ItemsInStock - 2

UPDATE products SET ItemsinStock = @ItemsInStock
WHERE Id = 1

Print @ItemsInStock
Commit Transaction</span>

Este é o script para a transação 1. Aqui começamos a transação e declaramos uma variável do tipo inteiro “@ItemsInStock”. O valor dessa variável é definido como o valor da coluna ItemsinStock para o registro com Id 1 da tabela de produtos. Em seguida, é adicionado um atraso de 12 segundos para que a transação 2 possa concluir sua execução antes da transação 1. Após o atraso, o valor da variável @ItemsInStock é decrementado em 2, significando a venda de 2 produtos.

Por fim, o valor da coluna ItemsinStock para o registro com Id 1 é atualizado com o valor da variável @ItemsInStock. Em seguida, imprimimos o valor da variável @ItemsInStock na tela e confirmamos a transação.

Na segunda instância do SSMS, adicionamos o script para a transação 2, que é o seguinte:
<span style="font-size: 14px;">USE pos;

-- Transaction 2

BEGIN TRAN

DECLARE @ItemsInStock INT

SELECT @ItemsInStock = ItemsInStock
FROM products WHERE Id = 1

WaitFor Delay '00:00:3'
SET @ItemsInStock = @ItemsInStock - 3

UPDATE products SET ItemsinStock = @ItemsInStock
WHERE Id = 1

Print @ItemsInStock
Commit Transaction</span>

O script da transação 2 é semelhante ao da transação 1. Porém, aqui na transação 2, o atraso é de apenas três segundos e o decréscimo no valor da variável @ItemsInStock é de três, pois é uma venda de três itens.

Agora, execute a transação 1 e depois a transação 2. Você verá a transação 2 concluindo sua execução primeiro. E o valor impresso para a variável @ItemsInStock será 9. Após algum tempo a transação 1 também completará sua execução e o valor impresso para sua variável @ItemsInStock será 10.



Ambos os valores estão errados, o valor real da coluna ItemsInStock para o produto com ID 1 deve ser 7.

OBSERVAÇÃO:

É importante observar aqui que o problema de atualização perdida ocorre apenas com níveis de isolamento de transação de leitura confirmada e leitura não confirmada. Com todos os outros níveis de isolamento de transação, esse problema não ocorre.

Nível de isolamento de transação repetível de leitura

Vamos atualizar o nível de isolamento de ambas as transações para leitura repetível e ver se ocorre o problema de atualização perdida. Mas antes disso, execute a seguinte instrução para atualizar o valor de ItemsInStock de volta para 12.
Update products SET ItemsinStock = 12

Script para transação 1
<span style="font-size: 14px;">USE pos;

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
-- Transaction 1

BEGIN TRAN
DECLARE @ItemsInStock INT

SELECT @ItemsInStock = ItemsInStock
FROM products WHERE Id = 1

WaitFor Delay '00:00:12'
SET @ItemsInStock = @ItemsInStock - 2

UPDATE products SET ItemsinStock = @ItemsInStock
WHERE Id = 1

Print @ItemsInStock
Commit Transaction</span>

Script para transação 2
<span style="font-size: 14px;">USE pos;

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
-- Transaction 2

BEGIN TRAN
DECLARE @ItemsInStock INT

SELECT @ItemsInStock = ItemsInStock
FROM products WHERE Id = 1

WaitFor Delay '00:00:3'
SET @ItemsInStock = @ItemsInStock - 3

UPDATE products SET ItemsinStock = @ItemsInStock
WHERE Id = 1

Print @ItemsInStock
Commit Transaction</span>

Aqui em ambas as transações, definimos o nível de isolamento para leitura repetível.

Agora execute a transação 1 e, em seguida, execute imediatamente a transação 2. Ao contrário do caso anterior, a transação 2 terá que esperar que a transação 1 seja confirmada. Depois disso, ocorre o seguinte erro para a transação 2:

Msg 1205, Nível 13, Estado 51, Linha 15

A transação (ID do processo 55) foi bloqueada em recursos de bloqueio com outro processo e foi escolhida como vítima de bloqueio. Execute novamente a transação.

Este erro ocorre porque a leitura repetitiva bloqueia o recurso que está sendo lido ou atualizado pela transação 1 e cria um deadlock na outra transação que tenta acessar o mesmo recurso.

O erro diz que a transação 2 tem um deadlock em um recurso com outro processo e que essa transação foi bloqueada pelo deadlock. Isso significa que a outra transação recebeu acesso ao recurso enquanto essa transação foi bloqueada e não recebeu acesso ao recurso.

Ele também diz para executar novamente a transação, pois o recurso está gratuito agora. Agora, se você executar a transação 2 novamente, verá o valor correto dos itens em estoque, ou seja, 7. Isso ocorre porque a transação 1 já diminuiu o valor do IteminStock em 2, a transação 2 diminui ainda mais em 3, portanto 12 – (2+ 3) =7.