Monitorando bloqueios com o uso de Recursive Common Table Expressions
Autor: Wagner Aparecido Miranda | Publicado: 2013-12-19T04:17:00-08:00


Nos últimos meses tenho presenciado diversas situações
envolvendo bloqueios. Isso não somente no ambiente de produção do meu atual
emprego, mas também em alguns clientes bem como eventuais dúvidas em sala de
aula, fóruns e comunidades. A última vez foi bem recente e por conta de alguns
bloqueios em conjunto com alguns BUGs de aplicação me deparei com uma base
absurdamente crítica, uma quantidade enorme de bloqueios e algumas dezenas de
milhões de reais envolvidas.

Já perdi a conta de quantas vezes vi ou ouvi falar de uma cadeia
extensa de bloqueios em um ambiente crítico com tudo "parado". Os
sintomas são bem clássicos, ou seja, CPU baixa, memória disponível e bastante
lentidão. Algumas consultas na sysprocesses, sys.dm_exec_requests, sys.dm_os_waiting_tasks
ou ainda as tradicionais sp_who2 e sp_lock são suficientes para diagnosticar
vários bloqueios. O grande desafio é encontrar por onde começar. Em alguns
ambientes, eliminar vários processos de uma vez ou reiniciar um servidor são
algumas possibilidades, mas em ambientes de missão crítica resolver o problema
ao invés de contorná-lo pode não ser uma opção, mas sim o único caminho. O que
fazer para encontrar os verdadeiros culpados? Quais são os processos que estão
"travando" tudo? Qual a "bendita" transação, usuário ou
aplicação que está provocando os incidentes?

Desde o lançamento da versão 2005, o SQL Server
melhorou consideravelmente as possibilidades de diagnóstico. No momento em que
ele passou a disponibilizar as DMVs e "abrir" a "caixa
preta" ficou muito fácil ser assertivo nos problemas e nas soluções. Outro
excelente recursos foi o uso das&nbsp;Common Table Expressions (CTEs)&nbsp;em sua modalidade recursiva (Recursive CTEs).
A combinação das CTEs recursivas com a natureza hierárquica das cadeias de
bloqueios através das DMVs possibilita ser muito preciso em encontrar o
"culpado" pela forte presença de bloqueios em um servidor SQL Server.

Um exemplo prático

Para
poder simular uma contensão no ambiente utilizarei uma pequena estrutura e
provocarei alguns bloqueios. O script abaixo cria cinco tabelas (T1 a T5) com
apenas uma única coluna (cod) e preenche essas tabelas com números de 1 a 5.

– Muda
o contexto de banco de dados
USE&nbsp;TempDB

–
Cria uma tabela
CREATE
TABLE&nbsp;T1 (COD&nbsp;TINYINT)

–
Insere os dados em T1
INSERT
INTO&nbsp;T1&nbsp;VALUES&nbsp;(1)
INSERT
INTO&nbsp;T1&nbsp;VALUES&nbsp;(2)
INSERT
INTO&nbsp;T1&nbsp;VALUES&nbsp;(3)
INSERT
INTO&nbsp;T1&nbsp;VALUES&nbsp;(4)
INSERT
INTO&nbsp;T1&nbsp;VALUES&nbsp;(5)

–
Cria réplicas
SELECT&nbsp;COD&nbsp;INTO&nbsp;T2&nbsp;FROM&nbsp;T1
SELECT&nbsp;COD&nbsp;INTO&nbsp;T3&nbsp;FROM&nbsp;T1
SELECT&nbsp;COD&nbsp;INTO&nbsp;T4&nbsp;FROM&nbsp;T1
SELECT&nbsp;COD&nbsp;INTO&nbsp;T5&nbsp;FROM&nbsp;T1

Agora
que estão presentes 5 tabelas (ou melhor dizendo 5 recursos), é possível
provocar uma cadeia de bloqueios entre processos que acessem esses recursos. O
primeiro passo é provocar uma contensão por parte de uma conexão. A consulta
abaixo (deve ser executada com o SQL Server Management Studio) bloqueará
exclusivamente as tabelas recém criadas, pois, a transação ficará em aberto.

–
Muda o contexto de banco de dados
USE&nbsp;TempDB;

–
Muda o nível de isolamento (o mais rígido)
SET
TRANSACTION ISOLATION LEVEL SERIALIZABLE

BEGIN
TRAN

&nbsp;&nbsp;&nbsp;&nbsp;– Lê
os dados da primeira tabela de forma exclusiva
&nbsp;&nbsp;&nbsp;&nbsp;SELECT&nbsp;COD&nbsp;FROM&nbsp;T1&nbsp;WITH&nbsp;(XLOCK)

&nbsp;&nbsp;&nbsp;&nbsp;–
Atualiza os dados da segunda tabela
&nbsp;&nbsp;&nbsp;&nbsp;UPDATE&nbsp;T2&nbsp;SET&nbsp;COD
= COD + 1

&nbsp;&nbsp;&nbsp;&nbsp;–
Exclui os dados da terceira tabela
&nbsp;&nbsp;&nbsp;&nbsp;DELETE FROM&nbsp;T3

&nbsp;&nbsp;&nbsp;&nbsp;–
Exclui os dados da quarta tabela com o uso do truncate
&nbsp;&nbsp;&nbsp;&nbsp;TRUNCATE TABLE&nbsp;T4

&nbsp;&nbsp;&nbsp;&nbsp;– Lê
os dados da quinta tabela de forma compartilhada, mas mantendo o bloqueio
&nbsp;&nbsp;&nbsp;&nbsp;SELECT&nbsp;COD&nbsp;FROM&nbsp;T5&nbsp;WITH&nbsp;(HOLDLOCK)

Um
comando SELECT demonstra os vários bloqueios impostos por essa transação.

–
Muda o contexto do Banco
USE&nbsp;TempDB;

SELECT
&nbsp;&nbsp;&nbsp;&nbsp;db_name(resource_database_id)&nbsp;As&nbsp;Banco,
T.name&nbsp;As&nbsp;Tabela,
&nbsp;&nbsp;&nbsp; request_mode&nbsp;As&nbsp;Modelo,
request_session_id&nbsp;As&nbsp;Sessao
FROM
&nbsp;&nbsp;&nbsp;&nbsp;sys.dm_tran_locks&nbsp;As&nbsp;B
&nbsp;&nbsp;&nbsp;&nbsp;INNER JOIN&nbsp;sys.tables&nbsp;As&nbsp;T&nbsp;ON&nbsp;B.resource_associated_entity_id
= T.object_id
WHERE
&nbsp;&nbsp;&nbsp; resource_type =&nbsp;‘OBJECT’&nbsp;AND&nbsp;resource_associated_entity_id
&gt; 10
ORDER
BY&nbsp;Tabela

O
resultado mostra alguns bloqueios sobre as tabelas da transação e mostra o SPID
da mesma (no meu caso é 52)

 

  
  
Banco

  
  
Tabela

  
  
Modelo

  
  
Sessão

 

  
  
tempdb

  
  
T1

  
  
X

  
  
52

 

  
  
tempdb

  
  
T2

  
  
X

  
  
52

 

  
  
tempdb

  
  
T3

  
  
X

  
  
52

 

  
  
tempdb

  
  
T4

  
  
Sch-M

  
  
52

 

  
  
tempdb

  
  
T5

  
  
S

  
  
52

 


Uma
vez que a sessão 52 (minha sessão no SSMS) esteja "segurando" os
recursos, os acessos a esses recursos tendem a ser bloqueados já que a maioria
dos bloqueios é exclusiva. O código abaixo em C# monta um pequeno aplicativo
com 25 conexões ao banco com o intuito de acessar os recursos que estão
bloqueados (T1 a T5).

using&nbsp;System;
using&nbsp;System.Collections.Generic;
using&nbsp;System.Threading;
using&nbsp;System.Data.Sql;
using&nbsp;System.Data.SqlClient;

namespace&nbsp;MB
{
&nbsp;&nbsp;&nbsp;&nbsp;class&nbsp;Program
&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;static
void&nbsp;Main(string[]
args)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Program&nbsp;p
=&nbsp;new&nbsp;Program();

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;string[]
strCmds =&nbsp;new string[25];

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
strCmds[0]&nbsp; =&nbsp;"UPDATE T1 SET COD = COD + 2";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
strCmds[1]&nbsp; =&nbsp;"SELECT COD FROM T2 WHERE COD &lt;
3";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
strCmds[2]&nbsp; =&nbsp;"SELECT COD FROM T2 WHERE COD &gt;
2";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
strCmds[3]&nbsp; =&nbsp;"DELETE FROM T5";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
strCmds[4]&nbsp; =&nbsp;"TRUNCATE TABLE T5";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
strCmds[5]&nbsp; =&nbsp;"SELECT COD FROM T4";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
strCmds[6]&nbsp; =&nbsp;"UPDATE T2 SET COD = 10 WHERE COD
= 1";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
strCmds[7]&nbsp; =&nbsp;"SELECT * FROM T1, T2, T3,
T5";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
strCmds[8]&nbsp; =&nbsp;"DELETE FROM T5 WHERE COD IN
(SELECT COD FROM T3)";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
strCmds[9]&nbsp; =&nbsp;"TRUNCATE TABLE T1";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strCmds[10]
=&nbsp;"SELECT * FROM T2, T4, T5";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strCmds[11]
=&nbsp;"DROP TABLE T1";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strCmds[12]
=&nbsp;"ALTER TABLE T2 ALTER COLUMN COD
CHAR(3)";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strCmds[13]
=&nbsp;"UPDATE T3 SET COD = COD +
20";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strCmds[14]
=&nbsp;"DELETE FROM T2";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strCmds[15]
=&nbsp;"SELECT COD FROM T1 WHERE COD &gt;
2";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strCmds[16]
=&nbsp;"INSERT INTO T1 VALUES (6)";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strCmds[17]
=&nbsp;"INSERT INTO T5 VALUES (7)";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strCmds[18]
=&nbsp;"INSERT INTO T5 VALUES (8)";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strCmds[19]
=&nbsp;"DELETE FROM T1";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strCmds[20]
=&nbsp;"DELETE FROM T2";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strCmds[21]
=&nbsp;"SELECT COD FROM T1 WITH
(XLOCK)";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strCmds[22]
=&nbsp;"UPDATE T3 SET COD = 1 WHERE COD
IN (SELECT COD FROM T4)";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strCmds[23]
=&nbsp;"SELECT COD FROM T1, T4";
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strCmds[24]
=&nbsp;"INSERT INTO T3 VALUES (10)";

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread[]
trds =&nbsp;new&nbsp;Thread[25];

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;i
= 0;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;string&nbsp;vApp
=&nbsp;"";

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(i
= 0; i &lt; 25; i++)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
vApp =&nbsp;"App"&nbsp;+
i.ToString("00");
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
trds[i] =&nbsp;new&nbsp;Thread(delegate() {
p.Manipula(strCmds[i], vApp); });
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
trds[i].Start();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread.Sleep(500);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
}

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public
void&nbsp;Manipula(string&nbsp;strCmd,&nbsp;string&nbsp;strApp)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;string&nbsp;strConn
=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Server=localhost;Database=TempDb;Integrated
Security=SSPI;"&nbsp;+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Connect
Timeout=7200;Application Name="&nbsp;+
strApp +&nbsp;";";

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SqlConnection&nbsp;cn
=&nbsp;new&nbsp;SqlConnection(strConn);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SqlCommand&nbsp;cmd
= cn.CreateCommand();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
cmd.CommandTimeout = 7200;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
cmd.CommandText = strCmd;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
cn.Open();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
cmd.ExecuteNonQuery();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Console.WriteLine("Um
processo foi eliminado");
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
}

&nbsp;&nbsp;&nbsp;
}
}

Como
nem sempre o Visual Studio está disponível e é necessário compilar o código, se
o SQL Server estiver instalado, então necessariamente o .NET Framework também
estará presente juntamente com o compilador. Para compilar esse código, basta
seguir as instruções:

<!--[if !supportLists]-->§&nbsp;
<!--[endif]-->Abra o Notepad

<!--[if !supportLists]-->§&nbsp;
<!--[endif]-->Cole o código

<!--[if !supportLists]-->§&nbsp;
<!--[endif]-->Salve o arquivo em algum local com a
extensão .cs (Ex: H:\MB\MB.cs)

<!--[if !supportLists]-->§&nbsp;
<!--[endif]-->Abra um prompt de comando

<!--[if !supportLists]-->§&nbsp;
<!--[endif]-->Navegue até o diretório
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727 (ou superior se houver .NET
Framework mais recente)

<!--[if !supportLists]-->§&nbsp;
<!--[endif]-->Digite csc /out:H:\MB\MB.exe
H:\MB\MB.cs e pressione ENTER (pressupondo que o arquivo seja H:\MB\MB.cs)

O
código possui uma matriz com 25 comandos que é executado a cada meio segundo.
Como a transação do SQL Server Management Studio (ID 52) está bloqueando vários
dos recursos, a cada meio segundo um bloqueio irá surgir. É possível verificar
as conexões e os bloqueios utilizando a consulta abaixo:

–
Verifica as sessões e os bloqueios
SELECT
&nbsp;&nbsp;&nbsp; s.session_id&nbsp;As&nbsp;Sessao,&nbsp;program_name&nbsp;As&nbsp;Aplicacao,
&nbsp;&nbsp;&nbsp; R.status&nbsp;As&nbsp;StatusRequisicao,
blocking_session_id&nbsp;As&nbsp;SessaoBloqueadora
FROM&nbsp;sys.dm_exec_sessions&nbsp;As&nbsp;S
LEFT
OUTER JOIN&nbsp;sys.dm_exec_requests&nbsp;As&nbsp;R&nbsp;ON&nbsp;S.session_id
= R.session_id
WHERE&nbsp;S.session_id
&gt; 50 and S.session_id !=&nbsp;@@spid&nbsp;

A
consulta acima mostra todas as sessões e suas respectivas requisições (se
houver) conforme a tabela abaixo:

 

  
  
Sessão

  
  
Aplicação

  
  
Status da
  Requisição

  
  
Sessão
  Bloqueadora

 

  
  
51

  
  
Microsoft SQL Server Management
  Studio

  
  
NULL

  
  
NULL

 

  
  
52

  
  
Microsoft SQL Server Management Studio
  – Query

  
  
NULL

  
  
NULL

 

  
  
54

  
  
App00

  
  
suspended

  
  
52

 

  
  
55

  
  
App01

  
  
suspended

  
  
52

 

  
  
56

  
  
App02

  
  
suspended

  
  
55

 

  
  
57

  
  
App03

  
  
suspended

  
  
52

 

  
  
58

  
  
App04

  
  
suspended

  
  
57

 

  
  
59

  
  
App05

  
  
suspended

  
  
52

 

  
  
60

  
  
App06

  
  
suspended

  
  
55

 

  
  
61

  
  
App07

  
  
suspended

  
  
52

 

  
  
62

  
  
App08

  
  
suspended

  
  
57

 

  
  
63

  
  
App09

  
  
suspended

  
  
54

 

  
  
64

  
  
App10

  
  
suspended

  
  
59

 

  
  
65

  
  
App11

  
  
suspended

  
  
54

 

  
  
66

  
  
App12

  
  
suspended

  
  
55

 

  
  
67

  
  
App13

  
  
suspended

  
  
62

 

  
  
68

  
  
App14

  
  
suspended

  
  
55

 

  
  
69

  
  
App15

  
  
suspended

  
  
54

 

  
  
70

  
  
App16

  
  
suspended

  
  
54

 

  
  
71

  
  
App17

  
  
suspended

  
  
57

 

  
  
72

  
  
App18

  
  
suspended

  
  
57

 

  
  
73

  
  
App19

  
  
suspended

  
  
54

 

  
  
74

  
  
App20

  
  
suspended

  
  
55

 

  
  
75

  
  
App21

  
  
suspended

  
  
54

 

  
  
76

  
  
App22

  
  
suspended

  
  
59

 

  
  
77

  
  
App23

  
  
suspended

  
  
59

 

  
  
78

  
  
App24

  
  
suspended

  
  
62

 


Para
que as sessões sejam mantidas exatamente com os mesmos IDs é importante que o
SQL Server Management Studio possua apenas três conexões (uma para o object
explorer, uma para a transação e uma para as consultas. As conexões devem ser
abertas exatamente nessa ordem e não deve haver ninguém conectado).

Como
pode ser observado, algumas sessões não possuem bloqueios enquanto outras
encontram-se bloqueadas. A coluna "Sessão Bloqueadora" mostra o ID da
sessão que está bloqueando uma outra sessão. Como pode ser observado, algumas
sessões como a 52 e a 54 são responsáveis por bloquear várias requisições que
por sua vez acabam bloqueandos outras e assim é montada uma cadeia de
bloqueios. É possível por exemplo visualizar que a sessão 52 bloqueia a sessão
54 que bloqueia a sessão 73 por exemplo. Isso mostra uma cadeia em três níveis
embora haja mais níveis na cadeia de bloqueios.

Em
um cenário desses, os iniciantes optariam por parar o serviço ou ainda eliminar
aleatoriamente algumas sessões até que um nível de estabilidade fosse obtido.
Tais medidas podem não ser muito efetivas para resolver o problema. Parar o
serviço por exemplo provocará uma indisponibilidade nas aplicações que dependem
do SQL Server. Eliminar sessões de forma aleatória provocará a reversão de
transações que podem significar graves problemas futuros (imagine um usuário
que mandou efetivar uma aplicação financeira ter a sua requisição cancelada).
Ainda que funcionem, evitar descobrir a causa real dos bloqueios pode fazer com
eles voltem mesmo após reiniciar um serviço por exemplo. Tais soluções são
ainda pouco factíveis em ambientes de missão crítica.

A
medida mais eficiente é identificar a sessão que está no topo da cadeia, ou
seja, qual a requisição que está provocando o bloqueio de outras sessões e
assim sucessivamente. Uma primeira tentativa, é identificar o maior bloqueador,
pois, tratar o maior bloqueador significa pode significar liberar uma grande
quantidade de sessões. A consulta abaixo mostra a lista do maior bloqueador.

SELECT&nbsp;blocking_session_id&nbsp;As&nbsp;Sessao,&nbsp;COUNT(session_id)&nbsp;As&nbsp;TotalBloqueios
FROM&nbsp;sys.dm_exec_requests
WHERE&nbsp;blocking_session_id
&gt; 0
GROUP
BY&nbsp;blocking_session_id
ORDER
BY&nbsp;TotalBloqueios&nbsp;DESC

A
lista abaixo mostra as quantidades de bloqueio:

 

  
  
Sessão

  
  
Total de
  Bloqueios

 

  
  
54

  
  
6

 

  
  
55

  
  
5

 

  
  
52

  
  
5

 

  
  
57

  
  
4

 

  
  
59

  
  
3

 

  
  
62

  
  
2

 


A
ideia de eliminar o maior bloqueador é interessante, mas será que ela é
"efetiva" ? Façamos um teste com os comandos abaixo:

KILL&nbsp;54

Anteriormente
haviam 25 bloqueios e a sessão 54 é responsável por 6 desses 25 bloqueios (mais
de 20%). Considerando que existem 28 sessões (minhas 3 e 25 da aplicação) matar
uma única sessão e liberar 20% dos bloqueios parece ser uma idéia bem
interessante. Após eliminar esse processo e efetuar novamente a consulta dos
maiores bloqueadores, é obtido o seguinte resultado:

 

  
  
Sessão

  
  
Total de
  Bloqueios

 

  
  
55

  
  
5

 

  
  
63

  
  
5

 

  
  
52

  
  
4

 

  
  
57

  
  
4

 

  
  
59

  
  
3

 

  
  
62

  
  
2

 

  
  
77

  
  
1

 


Ao
invés de restarem apenas 19 bloqueios (6 a menos em relação aos 25), restaram
24 bloqueios. Isso significa que eliminar a sessão 54 e seus seis bloqueios
reduziu a quantidade de bloqueios de 25 para 24 significando muito pouco em
relação ao total de bloqueios. Isso ocorre porque embora a sessões 54 tenha
sido eliminada, as sessões que estavam aguardando por ela agora estão
bloqueadas por outras sessões. Seguindo ainda a mesma linha, os maiores
bloqueadores são as sessões 55 e 63 empatando com 5 bloqueios cada totalizando
10 bloqueios. O comando abaixo elimina essas sessões:

KILL&nbsp;55
KILL&nbsp;63

Após
eliminar as sessões 55 e 63, a relação dos maiores bloqueadores é exibida na
tabela abaixo:

 

  
  
Sessão

  
  
Total de
  Bloqueios

 

  
  
52

  
  
4

 

  
  
56

  
  
4

 

  
  
57

  
  
4

 

  
  
65

  
  
4

 

  
  
59

  
  
3

 

  
  
62

  
  
2

 

  
  
77

  
  
1

 


No
início da análise havia 28 sessões e 25 bloqueios. Eliminou-se as sessões que
mantinham mais bloqueios e mesmo assim o número de bloqueios diminuiu
lentamente. Eliminou-se 3 sessões e o número de bloqueios caiu de 25 para 22.
Se o raciocínio for mantido poderá ser necessário eliminar muitos outros
bloqueios para que um resultado visível possa ser obtido. Cada sessão eliminada
é uma "esperança" de que as coisas voltem a funcionar, mas pode
significar uma transação incompleta e boas "dores de cabeça".

A
idéia de pesquisar os maiores bloqueadores é interessante, mas pode levar a
interpretações equivocadas. O interessante não é procurar os maiores
bloqueadores, pois, o resultado são quantos bloqueios "diretos" cada
sessão provoca. A sessões 54 provocava 6 bloqueios, mas se considerarmos que a
sessão 52 bloqueava a 54 seria visível que eliminar a sessão 52 é bem mais
efetivo já que mais bloqueios (quer sejam diretos ou indiretos) seriam
desfeitos. Se essa sessão bloqueasse somente a sessão 54 por exemplo, teríamos
5 bloqueios diretos (os que a sessão 52 bloqueia diretamente) e mais 6
bloqueios indiretos (os bloqueios impostos pela sessão 54) totalizando 9
bloqueios.

Esse
é a grande dificuldade quando se depara com cadeias de bloqueios. Como
identificar exatamente quais as sessões que são responsáveis pelo maior número
de bloqueios (diretos ou indiretos), ou seja, quais sessões que estão realmente
"travando" o banco ? Visualmente pode ser até fácil, mas em uma longa
cadeia de bloqueios, esse processo pode ser muito trabalhoso se feito de forma
visual. Um detalhe que pode ser muito útil é perceber que a cadeia de
bloqueios, ou seja, a relação entre as sessões e suas bloqueadoras é
hierárquica. Isso é uma excelente oportunidade para utilizar o recurso de CTEs
recursivas. Antes de propriamente prosseguir, é interessante que o aplicativo
MB seja fechado e reaberto. Isso fará com que os processos 54, 55 e 63 que
foram eliminados sejam considerados novamente.

O uso das Common Table Expressions Recursivas
(Recursive CTEs)

Não vou entrar nos detalhes das CTEs recursivas uma
vez que há vários assuntos sobre elas na Internet além do que as mesmas são
muito bem abordadas pelo colaborador&nbsp;Thiago Zavaschi&nbsp;em&nbsp;Entendendo as Common Table Expressions – CTE –
Parte 1. Eu já abordei um exemplo de CTE
recursiva emDicas e Truques sobre consultas complexas no SQL
Server, mas as apresentações do&nbsp;Zavaschi&nbsp;sobre CTEs (tanto em seus artigos quanto
no&nbsp;SQL Server Day)
são certamente superiores.

Uma
vez que exista uma relação hierárquica entre as sessões, é possível utilizar
uma CTE recursiva para explorar essas relações. Aconsulta abaixo demonstra a
quantidade real de bloqueios de cada sessão, ou seja, tanto os bloqueios que
uma sessão pode provocar diretamente em outras, quanto bloqueios provocados
indiretamente, ou seja, se A bloqueia B e B bloqueia C, indiretamente A
bloqueia C.

;WITH&nbsp;Sessoes
(Sessao, Bloqueadora)&nbsp;As&nbsp;(
&nbsp;&nbsp;&nbsp;&nbsp;SELECT&nbsp;Session_Id,
Blocking_Session_Id
&nbsp;&nbsp;&nbsp;&nbsp;FROM&nbsp;sys.dm_exec_requests&nbsp;As&nbsp;R
&nbsp;&nbsp;&nbsp;&nbsp;WHERE&nbsp;blocking_session_id
&gt; 0
&nbsp;&nbsp;&nbsp;&nbsp;UNION&nbsp;ALL
&nbsp;&nbsp;&nbsp;&nbsp;SELECT&nbsp;Session_Id,&nbsp;CAST(0&nbsp;As
SMALLINT)
&nbsp;&nbsp;&nbsp;&nbsp;FROM&nbsp;sys.dm_exec_sessions&nbsp;As&nbsp;S
&nbsp;&nbsp;&nbsp;&nbsp;WHERE&nbsp;EXISTS
(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SELECT&nbsp;*&nbsp;FROM&nbsp;sys.dm_exec_requests&nbsp;As&nbsp;R
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WHERE&nbsp;S.Session_Id
= R.Blocking_Session_Id)
&nbsp;&nbsp;&nbsp;&nbsp;AND NOT EXISTS&nbsp;(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SELECT&nbsp;*&nbsp;FROM&nbsp;sys.dm_exec_requests&nbsp;As&nbsp;R
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WHERE&nbsp;S.Session_Id
= R.Session_Id)
),

Bloqueios&nbsp;As&nbsp;(

&nbsp;&nbsp;&nbsp;&nbsp;SELECT
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Sessao, Bloqueadora, Sessao As Ref,
1 As Nivel
&nbsp;&nbsp;&nbsp;&nbsp;FROM&nbsp;Sessoes
&nbsp;&nbsp;&nbsp;&nbsp;UNION&nbsp;ALL
&nbsp;&nbsp;&nbsp;&nbsp;SELECT&nbsp;S.Sessao,
B.Sessao, B.Ref, Nivel + 1
&nbsp;&nbsp;&nbsp;&nbsp;FROM&nbsp;Bloqueios&nbsp;As&nbsp;B
&nbsp;&nbsp;&nbsp;&nbsp;INNER JOIN&nbsp;Sessoes&nbsp;As&nbsp;S&nbsp;ON&nbsp;B.Sessao
= S.Bloqueadora)

SELECT&nbsp;Ref&nbsp;As&nbsp;Sessao,
&nbsp;&nbsp;&nbsp;&nbsp;COUNT(DISTINCT&nbsp;R.Session_Id)&nbsp;As&nbsp;BloqueiosDiretos,
&nbsp;&nbsp;&nbsp;&nbsp;COUNT(DISTINCT&nbsp;B.Sessao)
– 1&nbsp;As&nbsp;BloqueiosTotal,
&nbsp;&nbsp;&nbsp;&nbsp;COUNT(DISTINCT&nbsp;B.Sessao)
–&nbsp;COUNT(DISTINCT&nbsp;R.Session_Id)
– 1&nbsp;AsBloqueiosIndiretos
FROM&nbsp;Bloqueios&nbsp;As&nbsp;B
&nbsp;&nbsp;&nbsp;&nbsp;INNER JOIN&nbsp;sys.dm_exec_requests&nbsp;As&nbsp;R
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ON&nbsp;B.Ref
= R.blocking_session_id
GROUP
BY&nbsp;Ref

O
resultado pode ser observado logo abaixo:

 

  
  
Sessão

  
  
Bloqueios Diretos

  
  
Bloqueios Total

  
  
Bloqueios
  Indiretos

 

  
  
52

  
  
5

  
  
25

  
  
20

 

  
  
54

  
  
6

  
  
6

  
  
0

 

  
  
55

  
  
5

  
  
5

  
  
0

 

  
  
57

  
  
4

  
  
4

  
  
0

 

  
  
59

  
  
3

  
  
3

  
  
0

 

  
  
62

  
  
2

  
  
2

  
  
0

 


Eliminar
a sessão 54 significa liberar 6 bloqueios diretos. A sessão 52 bloqueia apenas
5 sessões diretamente, mas indiretamente bloqueia outras 20 já que as 5 sessões
bloqueadas diretamente bloqueam outras sessões. A eliminação da sessão 52
libera outras 25 sessões e visivelmente é a medida mais efetiva para ajudar que
os bloqueios sejam resolvidos.

Uma
pequena mudança no código é capaz de mostrar os comandos executados por cada
uma das sessões que impõe bloqueios sobre as outras.

;WITH&nbsp;Sessoes
(Sessao, Bloqueadora)&nbsp;As&nbsp;(
&nbsp;&nbsp;&nbsp;&nbsp;SELECT&nbsp;Session_Id,
Blocking_Session_Id
&nbsp;&nbsp;&nbsp;&nbsp;FROM&nbsp;sys.dm_exec_requests&nbsp;As&nbsp;R
&nbsp;&nbsp;&nbsp;&nbsp;WHERE&nbsp;blocking_session_id
&gt; 0
&nbsp;&nbsp;&nbsp;&nbsp;UNION&nbsp;ALL
&nbsp;&nbsp;&nbsp;&nbsp;SELECT&nbsp;Session_Id,&nbsp;CAST(0&nbsp;As
SMALLINT)
&nbsp;&nbsp;&nbsp;&nbsp;FROM&nbsp;sys.dm_exec_sessions&nbsp;As&nbsp;S
&nbsp;&nbsp;&nbsp;&nbsp;WHERE&nbsp;EXISTS
(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SELECT&nbsp;*&nbsp;FROM&nbsp;sys.dm_exec_requests&nbsp;As&nbsp;R
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WHERE&nbsp;S.Session_Id
= R.Blocking_Session_Id)
&nbsp;&nbsp;&nbsp;&nbsp;AND NOT EXISTS&nbsp;(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SELECT&nbsp;*&nbsp;FROM&nbsp;sys.dm_exec_requests&nbsp;As&nbsp;R
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WHERE&nbsp;S.Session_Id
= R.Session_Id)
),

Bloqueios&nbsp;As&nbsp;(

&nbsp;&nbsp;&nbsp;&nbsp;SELECT
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Sessao, Bloqueadora, Sessao As Ref,
1 As Nivel
&nbsp;&nbsp;&nbsp;&nbsp;FROM&nbsp;Sessoes
&nbsp;&nbsp;&nbsp;&nbsp;UNION&nbsp;ALL
&nbsp;&nbsp;&nbsp;&nbsp;SELECT&nbsp;S.Sessao,
B.Sessao, B.Ref, Nivel + 1
&nbsp;&nbsp;&nbsp;&nbsp;FROM&nbsp;Bloqueios&nbsp;As&nbsp;B
&nbsp;&nbsp;&nbsp;&nbsp;INNER JOIN&nbsp;Sessoes&nbsp;As&nbsp;S&nbsp;ON&nbsp;B.Sessao
= S.Bloqueadora)

SELECT&nbsp;Ref&nbsp;As&nbsp;Sessao,
&nbsp;&nbsp;&nbsp;&nbsp;COUNT(DISTINCT&nbsp;R.Session_Id)&nbsp;As&nbsp;BloqueiosDiretos,
&nbsp;&nbsp;&nbsp;&nbsp;COUNT(DISTINCT&nbsp;B.Sessao)
– 1&nbsp;As&nbsp;BloqueiosTotal,
&nbsp;&nbsp;&nbsp;&nbsp;COUNT(DISTINCT&nbsp;B.Sessao)
–&nbsp;COUNT(DISTINCT&nbsp;R.Session_Id)
– 1&nbsp;AsBloqueiosIndiretos,

&nbsp;&nbsp;&nbsp;
(SELECT
TEXT FROM&nbsp;sys.dm_exec_sql_text(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (SELECT&nbsp;most_recent_sql_handle&nbsp;FROM&nbsp;sys.dm_exec_connections
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WHERE&nbsp;session_id
= B.Ref)))&nbsp;As&nbsp;Comando
FROM&nbsp;Bloqueios&nbsp;As&nbsp;B
&nbsp;&nbsp;&nbsp;&nbsp;INNER JOIN&nbsp;sys.dm_exec_requests&nbsp;As&nbsp;R
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ON&nbsp;B.Ref
= R.blocking_session_id
GROUP
BY&nbsp;Ref

O
resultado pode ser observado logo abaixo:

 

  
  
Sessão

  
  
Bloqueios Diretos

  
  
Bloqueios Total

  
  
Bloqueios
  Indiretos

  
  
Comando

 

  
  
52

  
  
5

  
  
25

  
  
20

  
  
– Muda o contexto de banco de
  dados&nbsp; USE TempDB;&nbsp;&nbsp;&nbsp;&nbsp; — M

 

  
  
54

  
  
6

  
  
6

  
  
0

  
  
(@1 int)UPDATE [T1] set [COD] =
  [COD]+@1

 

  
  
55

  
  
5

  
  
5

  
  
0

  
  
(@1 tinyint)SELECT [COD] FROM [T2]
  WHERE [COD]&lt;@1

 

  
  
57

  
  
4

  
  
4

  
  
0

  
  
DELETE FROM T5

 

  
  
59

  
  
3

  
  
3

  
  
0

  
  
SELECT COD FROM T4

 

  
  
62

  
  
2

  
  
2

  
  
0

  
  
DELETE FROM T5 WHERE COD IN (SELECT
  COD FROM T3)

 


Como
era de se esperar, a sessão 52 (a aberta pelo SSMS) é a sessão que está no topo
da cadeia de bloqueios. O interessante dessa última consulta é que não somente
foi possível visualizar a quantidade de bloqueios aberta, mas principalmente o
comando que proporcionou que a cadeia se formasse.

O
uso das Recursive CTEs não se limita apenas a descobrir qual é a sessão que
impõe mais bloqueios diretos e indiretos. Com o uso da CTE é possível exibir as
cadeias de bloqueios e visualizar o que acontecerá em um primeiro momento
quando a sessão 52 for eliminada. A consulta abaixo mostra as cadeias de
bloqueio provocadas pela sessão 52 de forma indireta (exatamente 20 registros conforme
a tabela acima).

;WITH&nbsp;Sessoes
(Sessao, Bloqueadora)&nbsp;As&nbsp;(
&nbsp;&nbsp;&nbsp;&nbsp;SELECT&nbsp;Session_Id,
Blocking_Session_Id
&nbsp;&nbsp;&nbsp;&nbsp;FROM&nbsp;sys.dm_exec_requests&nbsp;As&nbsp;R
&nbsp;&nbsp;&nbsp;&nbsp;WHERE&nbsp;blocking_session_id
&gt; 0
&nbsp;&nbsp;&nbsp;&nbsp;UNION&nbsp;ALL
&nbsp;&nbsp;&nbsp;&nbsp;SELECT&nbsp;Session_Id,&nbsp;CAST(0&nbsp;As
SMALLINT)
&nbsp;&nbsp;&nbsp;&nbsp;FROM&nbsp;sys.dm_exec_sessions&nbsp;As&nbsp;S
&nbsp;&nbsp;&nbsp;&nbsp;WHERE&nbsp;EXISTS
(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SELECT&nbsp;*&nbsp;FROM&nbsp;sys.dm_exec_requests&nbsp;As&nbsp;R
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WHERE&nbsp;S.Session_Id
= R.Blocking_Session_Id)
&nbsp;&nbsp;&nbsp;&nbsp;AND NOT EXISTS&nbsp;(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SELECT&nbsp;*&nbsp;FROM&nbsp;sys.dm_exec_requests&nbsp;As&nbsp;R
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WHERE&nbsp;S.Session_Id
= R.Session_Id)
),

Bloqueios&nbsp;As&nbsp;(

&nbsp;&nbsp;&nbsp;&nbsp;SELECT
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CAST(Sessao&nbsp;As&nbsp;VARCHAR(200))&nbsp;As&nbsp;Cadeia,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Sessao, Bloqueadora, 1&nbsp;As&nbsp;Nivel
&nbsp;&nbsp;&nbsp;&nbsp;FROM&nbsp;Sessoes
&nbsp;&nbsp;&nbsp;&nbsp;UNION&nbsp;ALL
&nbsp;&nbsp;&nbsp;&nbsp;SELECT&nbsp;CAST(B.Cadeia
+&nbsp;‘ -&gt; ‘&nbsp;+&nbsp;CAST(S.Sessao&nbsp;As
VARCHAR(5))&nbsp;As
VARCHAR(200)),
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; S.Sessao, B.Sessao, Nivel + 1
&nbsp;&nbsp;&nbsp;&nbsp;FROM&nbsp;Bloqueios&nbsp;As&nbsp;B
&nbsp;&nbsp;&nbsp;&nbsp;INNER JOIN&nbsp;Sessoes&nbsp;As&nbsp;S&nbsp;ON&nbsp;B.Sessao
= S.Bloqueadora)

SELECT&nbsp;Cadeia&nbsp;FROM&nbsp;Bloqueios
WHERE&nbsp;Nivel
= (SELECT&nbsp;MAX(Nivel)&nbsp;FROM&nbsp;Bloqueios)
ORDER
BY&nbsp;Cadeia

O
resultado é obtido na tabela abaixo (a formatação foi proposital):

 

  
  
Linha

  
  
Cadeia

  
  
Linha

  
  
Cadeia

 

  
  
01

  
  
52 -&gt; 54 -&gt; 63

  
  
11

  
  
52 -&gt; 55 -&gt; 74

 

  
  
02

  
  
52 -&gt; 54 -&gt; 65

  
  
12

  
  
52 -&gt; 57 -&gt; 58

 

  
  
03

  
  
52 -&gt; 54 -&gt; 69

  
  
13

  
  
52 -&gt; 57 -&gt; 61

 

  
  
04

  
  
52 -&gt; 54 -&gt; 70

  
  
14

  
  
52 -&gt; 57 -&gt; 71

 

  
  
05

  
  
52 -&gt; 54 -&gt; 73

  
  
15

  
  
52 -&gt; 57 -&gt; 72

 

  
  
06

  
  
52 -&gt; 54 -&gt; 75

  
  
16

  
  
52 -&gt; 59 -&gt; 64

 

  
  
07

  
  
52 -&gt; 55 -&gt; 56

  
  
17

  
  
52 -&gt; 59 -&gt; 76

 

  
  
08

  
  
52 -&gt; 55 -&gt; 60

  
  
18

  
  
52 -&gt; 59 -&gt; 77

 

  
  
09

  
  
52 -&gt; 55 -&gt; 66

  
  
19

  
  
52 -&gt; 62 -&gt; 67

 

  
  
10

  
  
52 -&gt; 55 -&gt; 68

  
  
20

  
  
52 -&gt; 62 -&gt; 78

 


A
exemplo da linha 01 pode-se ver claramente que a sessão 52 bloqueia a sessão 54
que por sua vez bloqueia a sessão 63. A tabela exibe os 20 bloqueios indiretos.
Eliminar por exemplo a sessão 52, irá liberar a sessão 54 que irá liberar a
sessão 63. A consulta mostra todas as liberações que irão ocorrer se a sessão
52 for eliminada. Não é necessário eliminá-la ainda.

As
consultas anteriores juntamente com essa última mostram o poder que as CTEs
podem realizar no monitoramento e resolução dos bloqueios já que esses são
apresentados em uma relação hierárquica. Ainda assim, se o objetivo era apenas
encontrar a sessão que mais promove bloqueios, há uma maneira bem mais direta.
O raciocínio é bem simples. A sessão que forma a hierarquia será a primeira a
efetuar um bloqueio. A sessão que é bloqueada primeiro irá naturalmente ser a
que estará esperando por mais tempo, pois, à medida que ela é bloqueada é que
os demais bloqueios se formam. A consulta abaixo demonstra esse raciocínio

SELECT
TOP&nbsp;10 Session_Id,
Blocking_Session_Id
FROM&nbsp;sys.dm_exec_requests
WHERE&nbsp;Blocking_Session_Id
&gt; 0
ORDER
BY&nbsp;Wait_Time&nbsp;DESC

A
consulta exibe o seguinte resultado:

 

  
  
Session_Id

  
  
Blocking_Session_Id

 

  
  
54

  
  
52

 

  
  
55

  
  
52

 

  
  
56

  
  
55

 

  
  
57

  
  
52

 

  
  
58

  
  
57

 

  
  
59

  
  
52

 

  
  
60

  
  
55

 

  
  
61

  
  
57

 

  
  
62

  
  
52

 

  
  
63

  
  
54

 


A
consulta retorna todas as requisições que não estão bloqueadas
(blocking_session_id &gt; 0) e as ordena pelo maior tempo de espera. A sessão
54 está bloqueada pela sessão 52 e é a sessão que espera a mais tempo. Isso é
um forte indício de que a sessão 52 possui forte influência na cadeia de
bloqueios. A resposta não é tão precisa quanto o uso das CTEs, mas normalmente
essa alternativa é muito certeira por conta da lógica.

A
idéia desse artigo é demonstrar como abordar longas cadeias de bloqueios em
ambientes de produção. Normalmente quando o bloqueio é formado não há um aviso
formal ao responsável pelo banco de dados. Isso é natural, pois, os bloqueios
são necessários para garantir a integridade dos dados (principalmente na ótica
pessimista presença na maioria dos bancos de dados). A demora no processo de
resolução desses bloqueios (seja por conexões órfãs, transações longas, etc)
pode formar um situação bem crítica. Um bloqueio de 10ms talvez passe
despercebido, mas uma cadeia de bloqueios formada por várias sessões e imposta
há alguns minutos provavelmente será descoberta e o DBA não terá tempo para
tentar analisar qual a sessão que provoca tantos bloqueios. Abordei aqui as
principais técnicas que costumo utilizar para encontrá-los.

Os
códigos abordam um cenário muito simplista. Nem sempre há uma única cadeia de
bloqueios. É possível que haja vários processos montado extensas cadeias. É
possível também que as cadeias possuam mais de três níveis de aninhamento e
assim por diante. Os códigos mostram o caminho das pedras, mas certamente podem
ser otimizados. Espero que esse artigo sirva de subsídio para entender e lidar
com os bloqueios quando eles incomodarem. Por fim, matar a sessão 52, irá fazer
com que todas as sessões sejam liberadas e o SQL Server se resolva. Aos
curiosos, vale a pena efetuar um KILL e conferir o resultado das consultas repassadas
até então.