Uma das correções incluídas na atualização cumulativa 11 para SQL Server 2008 R2 Service Pack 2 aborda um "bloqueio incorreto" que pode ocorrer em um cenário específico (explicado posteriormente neste artigo). Infelizmente, a correção introduz um novo bug, em que as consultas SELECT em RCSI (leia o isolamento de instantâneo confirmado) começam a receber bloqueios compartilhados por intenção no nível da tabela. Como consequência, você pode ver um aumento no bloqueio (e potencialmente deadlocking) para consultas RCSI após a aplicação do 2008 R2 SP2 CU11 (ou posterior).
Isso será uma surpresa indesejável para qualquer pessoa acostumada a leitores que não bloqueiem escritores (e vice-versa) ao usar RCSI. Não há correção para o bug RCSI no momento da escrita. Na verdade, o item Connect criado por Eugene Karpovich para relatar o problema foi encerrado como "Não será corrigido", embora eu entenda que essa decisão está atualmente em revisão.
Normalmente, esse problema pode não ser uma preocupação tão grande, porque as atualizações cumulativas geralmente não são tão amplamente aplicadas quanto os service packs completos. No entanto, a Microsoft anunciou recentemente que haverá um Final Service Pack 3 para SQL Server 2008 R2. Este service pack será um simples acúmulo de atualizações cumulativas do SP2 existentes (até e incluindo CU13), mas sem novas correções. O resultado de tudo isso é que, a menos que algo mude nesse meio tempo, os usuários que aplicam o SP3 de repente começarão a ser afetados pelo bug RCSI introduzido no CU11.
editar:Pouco antes da publicação deste artigo, a Microsoft confirmou que essa regressão será corrigida no SP3.
O mesmo bug de "bloqueio incorreto" (cuja correção introduz o novo bug) também foi corrigido na atualização cumulativa 8 para SQL Server 2012 Service Pack 1 conforme descrito em KB2923460. A correção para o SQL Server 2012 é diferente e não introduzir o novo problema RCSI.
O SQL Server 2014 nunca foi afetado por nenhum dos problemas, até onde sei. Certamente não há documentação para indicar o contrário, e os testes que realizei em 2014 RTM, CU1 e CU2 não reproduzem nenhum dos bugs.
O bug RCSI 2008 R2
Uma consulta SELECT em execução em RCSI normalmente usa apenas um bloqueio de estabilidade de esquema (Sch-S), que é compatível com todos os outros bloqueios, exceto um bloqueio de modificação de esquema (Sch-M). Quando o CU11 (ou posterior) é aplicado a uma instância do SQL Server 2008 R2, essas consultas começam a ter um bloqueio de compartilhamento de intenção (Tab-IS) em nível de tabela. O seguinte script de teste pode ser usado para demonstrar a diferença de comportamentos:
USE master; GO SET TRANSACTION ISOLATION LEVEL READ COMMITTED; SET NOCOUNT ON; GO CREATE DATABASE RCSI; GO ALTER DATABASE RCSI SET READ_COMMITTED_SNAPSHOT ON; GO ALTER DATABASE RCSI SET ALLOW_SNAPSHOT_ISOLATION OFF; GO USE RCSI; GO CREATE TABLE dbo.Test ( id integer IDENTITY NOT NULL, col1 integer NOT NULL, CONSTRAINT PK_Test PRIMARY KEY CLUSTERED (id) ); GO INSERT dbo.Test (col1) VALUES (1), (2), (3), (4); GO -- Show locks DBCC TRACEON (1200, 3604, -1) WITH NO_INFOMSGS; SELECT * FROM dbo.Test; DBCC TRACEOFF (1200, 3604, -1) WITH NO_INFOMSGS; GO ALTER DATABASE RCSI SET SINGLE_USER WITH ROLLBACK IMMEDIATE; USE master; DROP DATABASE RCSI;
Quando executado em uma instância do SQL Server 2008 R2 sem o bug, a saída de depuração mostra um único bloqueio Sch-S obtido para a instrução de teste conforme o esperado:
Processo adquirindo bloqueio Sch-S em OBJECT:7:2105058535:0 resultado:OK
Processo liberando bloqueio em OBJECT:7:2105058535:0
Quando executado no SQL Server 2008 R2 build 10.50.4302 (ou superior), a saída é semelhante a:
Processo adquirindo bloqueio de IS em OBJECT:7:2105058535:0 resultado:OK
Processo liberando bloqueio em OBJECT:7:2105058535:0
Observe que o bloqueio Sch-S foi substituído por um bloqueio Tab-IS.
Implicações e Mitigações
Um bloqueio de intenção compartilhada (IS) ainda é um bloqueio muito compatível, mas não é tão amigável à simultaneidade quanto o Sch-S. A matriz de compatibilidade de bloqueio mostra que um bloqueio IS está em conflito com:
- Sch-M (modificação de esquema) – conforme Sch-S
- BU (atualização em massa)
- X (exclusivo)
A incompatibilidade com bloqueios exclusivos (X) significa que uma leitura sob RCSI será bloqueada se um processo simultâneo mantiver um bloqueio exclusivo no mesmo recurso. Da mesma forma, um gravador que precisa de um bloqueio exclusivo bloqueará se um leitor RCSI concorrente mantiver um bloqueio IS. Bloqueios exclusivos são obtidos sempre que os dados são modificados e mantidos até o final da transação, portanto, o efeito do bug é que os leitores sob RCSI serão bloqueados por gravadores simultâneos (e vice-versa) quando não eram antes da aplicação da CU11.
Um fator atenuante significativo é que o bug causa apenas um erro no nível da tabela bloqueio compartilhado por intenção a ser obtido. Um escritor simultâneo que precisa de um nível de tabela bloqueio exclusivo causará bloqueio (e potencialmente um impasse). No entanto, escritores simultâneos que exigem apenas bloqueios exclusivos em um nível inferior (por exemplo, linha, página ou partição) não causar bloqueio ou impasse. No nível da tabela, esses gravadores adquirirão apenas um bloqueio de intenção exclusiva (IX), que é compatível com Tab-IS. Os bloqueios exclusivos obtidos em níveis mais baixos de granularidade não causarão conflito.
Na maioria dos sistemas, os bloqueios exclusivos de nível de tabela (Tab-X) serão relativamente incomuns. A menos que solicitado explicitamente usando uma dica TABLOCKX, algumas causas possíveis de um bloqueio Tab-X são:
- Bloqueie o escalonamento de uma granularidade mais baixa
- Usando SERIALIZABLE sem um índice de suporte para bloqueios de intervalo de teclas
Uma solução técnica é adicionar a dica de tabela (redundante)
WITH (READCOMMITTED)
para todas as tabelas em todas as consultas executadas em RCSI. Isso acontece para contornar o bug, de modo que apenas um bloqueio Sch-S é usado, mas dificilmente é uma proposta prática. Apesar dessas atenuações, usar o Tab-IS para uma consulta somente leitura em RCSI ainda é um comportamento incorreto. Espero que possa ser corrigido para o SQL Server 2008 R2 antes do lançamento do Service Pack 3.
O bug "Impasse incorreto"
Como mencionado anteriormente, o bug RCSI é introduzido como um efeito colateral de uma correção para um bug de "bloqueio incorreto". Esse problema anterior está documentado para SQL Server 2008 R2 em KB2929464 e para SQL Server 2012 em KB2923460. Nenhum dos documentos é um modelo de clareza (ou precisão), mas a questão subjacente é bastante interessante, então quero passar um pouco de tempo analisando-a aqui.
Essencialmente, o deadlock ocorre quando:
- Três ou mais transações simultâneas lidas na mesma tabela
- As dicas UPDLOCK e TABLOCK são usadas nos três casos
- A configuração do banco de dados READ_COMMITTED_SNAPSHOT está ATIVADA
Observe que não importa em qual nível de isolamento as transações são executadas. Para reproduzir o bug, primeiro execute o script de configuração abaixo:
USE master; GO SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO CREATE DATABASE IncorrectDeadlock; GO ALTER DATABASE IncorrectDeadlock SET READ_COMMITTED_SNAPSHOT ON; GO USE IncorrectDeadlock; GO CREATE TABLE dbo.Test ( id integer IDENTITY NOT NULL, col1 integer NOT NULL, CONSTRAINT PK_Test PRIMARY KEY CLUSTERED (id) ); GO INSERT dbo.Test (col1) VALUES (1);
Em seguida, execute o seguinte script em três conexões separadas (observe que a transação é deixada aberta):
USE IncorrectDeadlock; GO SET TRANSACTION ISOLATION LEVEL READ COMMITTED; GO BEGIN TRANSACTION; SELECT T.id, T.col1 FROM dbo.Test AS T WITH (UPDLOCK, TABLOCK);
Neste ponto, a primeira sessão terá retornado um conjunto de resultados e as outras duas serão bloqueadas. O "impasse incorreto" surge quando a primeira sessão conclui sua transação (commitindo ou revertendo). Quando isso ocorrer, uma das outras duas sessões reportará um deadlock:
O impasse ocorre porque as duas sessões bloqueadas anteriormente contêm Tab-IX (exclusivo de intenção de nível de tabela) e ambas desejam converter seu bloqueio em Tab-X (exclusivo de nível de tabela). O Tab-IX é compatível com outro Tab-IX, mas não com o Tab-X. Este é um impasse de conversão (e a ironia aqui é que UPDLOCK é frequentemente usado para evitar impasses de conversão).
Sinta-se à vontade para variar o nível de isolamento da transação para as três consultas conforme desejar. O deadlock ocorrerá independentemente, desde que o RCSI esteja habilitado, com os mesmos bloqueios envolvidos. Quando os testes forem concluídos, remova o banco de dados de teste:
USE IncorrectDeadlock; ALTER DATABASE IncorrectDeadlock SET SINGLE_USER WITH ROLLBACK IMMEDIATE; USE master; DROP DATABASE IncorrectDeadlock;
Análise e explicação
Eu pessoalmente não me lembro de ter usado UPDLOCK e TABLOCK juntos no meu código. Para mim, essa combinação de dicas parece intuitivamente estranha porque o SQL Server não possui um bloqueio de atualização em nível de tabela . Então, o que isso significa especificar dicas UPDLOCK e TABLOCK juntas?
A documentação diz o seguinte:
UPDLOCK
Especifica que os bloqueios de atualização devem ser obtidos e mantidos até que a transação seja concluída. UPDLOCK usa bloqueios de atualização para operações de leitura apenas em nível de linha ou nível de página. Se UPDLOCK for combinado com TABLOCK, ou um bloqueio em nível de tabela for usado por algum outro motivo, um bloqueio exclusivo (X) será usado.
Isso sugere que a combinação de dicas deve resultar em um único bloqueio de tabela exclusivo. Na verdade, essa não é toda a história:
No SQL Server 2000, a combinação de dicas UPDLOCK e TABLOCK resulta em Tab-S (um bloqueio de tabela compartilhada) sendo obtido seguido pela conversão em Tab-X (bloqueio de tabela exclusivo) em todos os níveis de isolamento, exceto READ UNCOMMITTED. Essa sequência de bloqueios pode resultar em um impasse em que três ou mais sessões estão envolvidas:duas sessões adquirem Tab-S e ambas aguardam a outra para converter em Tab-X. Em READ UNCOMMITTED, o SQL Server 2000 usa Sch-S e depois Tab-X, que não é propenso a deadlock (apenas bloqueio normal).
No SQL Server 2005 em diante (sem a correção do bug) os bloqueios feitos dependem somente se o RCSI está ativado ou não. Se o RCSI estiver ativado, todos os níveis de isolamento pegue o Tab-IX e depois converta para o Tab-X. Essa sequência causa o impasse que os endereços de correção de bugs.
Se o RCSI não estiver habilitado, os níveis de isolamento correspondentes se comportarão como no SQL Server 2000 (tomando Tab-S e depois convertendo para Tab-X). O nível de isolamento de instantâneo (novo para 2005) leva Sch-S seguido por Tab-X. Como consequência, SI e READ UNCOMMITTED são os únicos níveis de isolamento não propensos a esse deadlock no cenário UPDLOCK, TABLOCK quando o RCSI não está habilitado.
A correção do impasse
A correção altera os bloqueios realizados quando UPDLOCK e TABLOCK são especificados juntos, para todos os níveis de isolamento , e independentemente se o RCSI está ativado ou não. Depois que a correção é aplicada, UPDLOCK e TABLOCK fazem com que o mecanismo adquira Tab-SIX (nível de tabela compartilhado com intenção exclusiva), que é então convertido em Tab-X.
Isso evita o cenário de impasse porque o Tab-SIX é incompatível com outro Tab-SIX. Lembre-se, o impasse ocorreu quando dois processos mantiveram o Tab-IX esperando para converter em Tab-X. Com o Tab-IX substituído pelo Tab-SIX, não é possível que ambos segurem o Tab-SIX ao mesmo tempo. O resultado é um cenário de bloqueio normal em vez de um impasse.
Considerações finais
A correção de "impasse incorreto" resolve um cenário de impasse específico, mas ainda não resulta no comportamento que imagino que as pessoas especificando UPDLOCK e TABLOCK previsto. Se o SQL Server tivesse um bloqueio Tab-U (atualização em nível de tabela), isso impediria alterações simultâneas na tabela, mas permitiria leitores simultâneos. Isso é o que eu imagino que a intenção das pessoas usando essas dicas juntas seria, e eu posso ver como isso pode ser útil.
A implementação atual (onde Tab-X é finalmente usado em vez do Tab-U ausente) não corresponde a essa expectativa porque Tab-X impede leituras simultâneas (a menos que um nível de isolamento de controle de versão de linha seja usado). Podemos também especificar TABLOCKX em muitos casos. O fato de a correção também apresentar um novo bug (somente para usuários do SQL Server 2008 R2) também é lamentável, principalmente se o bug for incluído no 2008 R2 SP3.
Observe que a correção de deadlock não está sendo disponibilizada para versões do SQL Server anteriores à 2008 R2. Essas versões continuarão a ter o comportamento de bloqueio complexo para UPDLOCK e TABLOCK conforme descrito acima.
Meus agradecimentos a Eugene Karpovich, que primeiro chamou minha atenção para esse problema em um comentário ao meu artigo sobre Modificações de dados sob RCSI.