Data

9 jul, 2013

Entendendo Transaction Isolation Level no SQL Server

Publicidade

O nível de isolamento (ou Isolation Level, em inglês) no SQL Server controla o comportamento dos bloqueios e controle de versão das linhas e instruções.

O SQL Server é um SGBD que implementa as propriedades ACID, afim de garantir Atomicidade, Consistência, Isolamento e Durabilidade de cada transação efetuada e o nível de isolamento está intimamente ligado a isso.

Existem quatro níveis de isolamento, READ UNCOMMITED, READ COMMITED, REPEATABLE READ e SERIALIZABLE. Neste artigo, vamos falar um pouco sobre cada um e simular um ambiente para observar os resultados e comportamentos de cada nível.

O READ UNCOMMITED é o nível mais baixo de isolamento do SQL Server, também conhecido como concorrência otimista. Quando este nível de isolamento é configurado, as transações conseguem ler registros que estão sendo alterado por outra transação, mesmo que um COMMIT\ROLLBACK ainda não tenha sido executado.  Por um lado, essa configuração é boa, pois é como se não existisse bloqueios sobre os registros e com isso causando menos LOCKS; por outro lado, esse processo permite leitura suja dos dados, isso porque a transação de leitura lê dados ainda não confirmados.

O código abaixo simula um ambiente com READ UNCOMMITED configurado:

Abra uma sessão, e execute o trecho de código abaixo:

-- Transação 1
Set Transaction Isolation Level Read Uncommitted
Begin Transaction
Select SalesQuota From sales.SalesPerson
Where BusinessEntityID = 275
GO
Update Sales.SalesPerson Set SalesQuota = 100.00
Where BusinessEntityID = 275
GO
Select SalesQuota From sales.SalesPerson
Where BusinessEntityID = 275
-- Rollback

Neste trecho de código (transação 1) o nível de isolamento é configurado para READ UNCOMMITED e em seguida é aberta uma transação para realizar o UPDATE no campo SalesQuota.

Repare que antes do UPDATE o valor era de R$ 300 mil e em seguida foi alterado para R$ 100; porém, a transação ainda não foi confirmada (COMMIT) ou abortada (ROLLBACK), com isso a alteração ainda está em memoria e não persistida em disco.

Para simular a leitura de dados “sujos”, abra uma nova sessão e execute o código abaixo:

-- Transação 2
Set Transaction Isolation Level Read Uncommitted
Select BusinessEntityID, TerritoryID, SalesQuota From sales.SalesPerson
Where BusinessEntityID = 275

Repare que o select irá retornar o valor de R$ 100. Aqui mora um dos grandes problemas em habilitar READ UNCOMMITED. Imagine uma situação que por algum motivo fosse executado ROLLBACK no UPDATE (transação 1). Isso faria o valor do campo SalesQuota voltar para R$ 300 mil e o select da transação 2 já retornou para o usuário o valor de R$ 100. Fazendo com que a transação retornasse informações erradas, pois na verdade o valor do campo é R$ 300 mil e não R$ 100.

O READ COMMITED é o modo padrão do SQL Server e garante que as leituras sejam somente de informações já confirmadas (COMMIT), ou seja, os dados retornados já estão em disco – isso elimina o problema de leituras sujas mencionadas no exemplo acima de READ UNCOMMITED.

Para exemplificar, abra uma nova conexão e execute o código a seguir:

-- Transação 1
Set Transaction Isolation Level Read Committed
Begin Transaction
Select SalesQuota From sales.SalesPerson
Where BusinessEntityID = 275
GO
Update Sales.SalesPerson Set SalesQuota = 100.00
Where BusinessEntityID = 275
GO
Select SalesQuota From sales.SalesPerson
Where BusinessEntityID = 275
-- Rollback

Abra outra conexão e execute o código abaixo:

-- Transação 2
Set Transaction Isolation Level Read Committed
Select BusinessEntityID, TerritoryID, SalesQuota From sales.SalesPerson
Where BusinessEntityID = 275

Repare que o select (transação 2) não retornou nada e está em execução. Isso ocorre porque o UPDATE (transação 1) gerou  bloqueio exclusivo sobre este registro, e com isso a transação 2 não consegue realizar o select, pois o UPDATE ainda não foi confirmado (COMMIT) ou abortado (ROLLBACK).

A view de sistema sys.dm_tran_locks exibe um registro para cada bloqueio atualmente em execução. Abra uma nova query e execute a view. Observe que irá existir um registro onde o campo Request_Status será WAIT. Isso significa que uma transação está esperando por outra para concluir suas operações. O READ COMMITED, diferente de READ UNCOMMITED, não permite leitura de dados sujos; por outro lado aumenta a concorrência nos registros por causas dos LOCKS.

O REPEATABLE READ, este nível de isolamento garante que um registro que está sendo lido por uma transação não sofra alteração até que a leitura termine, impedindo que ocorra Dirty Reads e Non-Repeatble Read. O REPEATABLE READ mantém LOCKS de leituras até o final da transação.

Abra uma sessão e execute o código a seguir:

-- Transação 1
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
Begin Transaction
Select BusinessEntityID, Bonus From Sales.SalesPerson
Where BusinessEntityID = 275
WAITFOR DELAY '00:00:10'
Rollback

Abra outra sessão e execute o techo de código abaixo:

-- Transação 2
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
Begin Transaction
Update Sales.SalesPerson Set Bonus = 2500.00
Where BusinessEntityID = 275
-- Rollback

O select executado pela primeira transação gerou um LOCK sobre esse registro, e quando executado, o UPDATE (transação 2) ficou bloqueado gerando um WAIT para esse registro. Observe que após o termino da transação 1 (estipulado pelo WATFOR DELAY) o comando ROLLBACK foi executado, com isso retirou o LOCK sobre o registro e consequentemente liberando o UPDATE. Antes de habilitar este nível de isolamento, verifique se é realmente necessário.

O SERIALIZABLE, é o nível de isolamento mais restritivo. Bloqueia todas as modificações nos dados, não se importando se parte de um UPDATE, INSERT, DELETE. Quando uma transação com essas operações é executada, é gerado um LOCK sobre o recurso e se outra transação tentar efetuar alguma operação, consequentemente será gerado um WAIT até que a primeira transação termine. O SERIALIZABLE é mais restritivo que REPEATABLE READ, pois causa LOCKS não só em transações de UPDATE , mas também em transações de INSERT.

Para exemplificar, abra uma nova query e execute o código:

-- Transação 1
Set Transaction Isolation Level SERIALIZABLE
Begin Transaction
Select BusinessEntityID, TerritoryID, SalesQuota, Bonus From sales.SalesPerson
Where Bonus between 1 and 1000
And Bonus is not null
-- Rollback

Abra uma nova query e execute a segunda transação:

-- Transação 2
Set Transaction Isolation Level SERIALIZABLE
Begin Transaction
Insert Sales.SalesPerson (BusinessEntityID, TerritoryID, SalesQuota, Bonus, CommissionPct, SalesYTD, SalesLastYear, rowguid, ModifiedDate)
Values (271, null, null, 999, 5000.00, 0.012, 3763178, NEWID(), GETDATE())
-- Rollback

Faça um select na view sys.dm_tran_locks e observe os bloqueios adquiridos com essas operações.

Para finalizar, não existe uma fórmula pra dizer qual é o melhor nível de isolamento. Isso muda de ambiente para ambiente e entre vários fatores. O ideal é que conheça cada um deles analisando os pontos positivos e negativos e também seu ambiente, para assim, aplicar o nível de isolamento que mais se enquadra no seu ambiente.

Obs: O Banco de Dados utilizado neste é artigo foi o AdventureWorks2008R2, está disponível para download neste link.

Grande abraço!