Entender e resolver problemas de bloqueio do SQL Server
Aplica-se a: SQL Server (todas as versões com suporte), Instância Gerenciada de SQL do Azure
Número original do KB: 224453
Objetivo
O artigo descreve o bloqueio no SQL Server e demonstra como solucionar problemas e resolver o bloqueio.
Neste artigo, o termo conexão refere-se a uma única sessão conectada do banco de dados. Cada conexão aparece como uma ID de sessão (SPID) ou session_id em muitos DMVs. Cada um desses SPIDs geralmente é chamado de processo, embora não seja um contexto de processo separado no sentido usual. Em vez disso, cada SPID consiste nos recursos e estruturas de dados do servidor necessárias para atender às solicitações de uma única conexão de um determinado cliente. Um aplicativo cliente único pode ter uma ou mais conexões. Da perspectiva do SQL Server, não há diferença entre várias conexões de um único aplicativo cliente em um único computador cliente e várias conexões de vários aplicativos cliente ou vários computadores cliente; elas são atômicas. Uma conexão pode bloquear outra conexão, independentemente do cliente de origem.
Observação
Este artigo se concentra em instâncias do SQL Server, incluindo Instâncias Gerenciadas de SQL do Azure. Para obter informações específicas sobre como solucionar o bloqueio no Banco de dados SQL do Azure, consulte Entender e resolver problemas de bloqueio do Banco de dados SQL do Azure.
O que é o bloqueio
O bloqueio é uma característica inevitável e intencional de qualquer RDBMS (sistema de gerenciamento de banco de dados relacional) com simultaneidade baseada em bloqueio. Conforme mencionado anteriormente, no SQL Server, o bloqueio ocorre quando uma sessão mantém um bloqueio em um recurso específico e um segundo SPID tenta adquirir um tipo de bloqueio conflitantes no mesmo recurso. Normalmente, o intervalo de tempo para o qual o primeiro SPID bloqueia o recurso é pequeno. Quando a sessão proprietária libera o bloqueio, a segunda conexão é liberada para adquirir seu próprio bloqueio no recurso e continuar o processamento. O bloqueio, conforme descrito aqui, é um comportamento normal e pode acontecer muitas vezes ao longo de um dia sem nenhum efeito perceptível no desempenho do sistema.
O contexto de duração e de transação de uma consulta determina por quanto tempo seus bloqueios são mantidos e seus efeitos em outras consultas. Se a consulta não for executada em uma transação (e nenhuma dica de bloqueio for usada), os bloqueios para instruções SELECT só serão mantidos em um recurso no momento em que ela estiver realmente sendo lida, não durante a consulta. Para instruções INSERT, UPDATE e DELETE, os bloqueios são mantidos durante a consulta, tanto para a consistência de dados quanto para permitir que a consulta seja revertida, se necessário.
Para consultas executadas em uma transação, a duração para a qual os bloqueios são mantidos é determinada pelo tipo de consulta, pelo nível de isolamento da transação e se as dicas de bloqueio são usadas na consulta. Para obter uma descrição de bloqueio, dicas de bloqueio e níveis de isolamento da transação, consulte os seguintes artigos:
- Bloqueio no Mecanismo de Banco de Dados
- Personalizando bloqueio e controle de versão de linha
- Modos de Bloqueio
- Compatibilidade de Bloqueios
- Níveis de Isolamento Baseados em Controle de Versão de Linha no Mecanismo de Banco de Dados
- Transações
Quando o bloqueio persiste até o ponto em que há um efeito prejudicial no desempenho do sistema, isso ocorre devido a um dos seguintes motivos:
Um SPID mantém bloqueios em um conjunto de recursos por um longo período de tempo antes de liberá-los. Esse tipo de bloqueio se resolve sozinho ao longo do tempo, mas pode causar degradação do desempenho.
Um SPID mantém bloqueios em um conjunto de recursos e nunca os libera. Esse tipo de bloqueio não se resolve sozinho e impede o acesso aos recursos afetados indefinidamente.
No primeiro cenário, a situação pode ser muito fluida, pois os SPIDs diferentes causam o bloqueio em recursos diferentes ao longo do tempo, criando um destino móvel. Essas situações são difíceis de solucionar usando o SQL Server Management Studio para restringir o problema a consultas individuais. Por outro lado, a segunda situação resulta em um estado consistente que pode ser mais fácil de diagnosticar.
Aplicativos e bloqueio
Pode haver uma tendência de se concentrar em problemas de plataforma e de ajuste no lado do servidor ao enfrentar um problema de bloqueio. No entanto, dar atenção apenas ao banco de dados pode não levar a uma resolução e pode absorver o tempo e a energia melhor direcionados para examinar o aplicativo cliente e as consultas que ele envia. Independentemente do nível de visibilidade que o aplicativo expõe em relação às chamadas de banco de dados feitas, um problema de bloqueio, no entanto, frequentemente exige a inspeção das instruções SQL exatas enviadas pelo aplicativo e o comportamento exato do aplicativo em relação ao cancelamento de consulta, ao gerenciamento de conexão, à busca de todas as linhas de resultado e assim por diante. Se a ferramenta de desenvolvimento não permitir controle explícito sobre o gerenciamento de conexões, cancelamento de consulta, tempo limite de consulta, busca de resultados e assim por diante, os problemas de bloqueio podem não ser resolvidos. Esse potencial deve ser examinado de perto antes de selecionar uma ferramenta de desenvolvimento de aplicativos para o SQL Server, especialmente para ambientes OLTP sensíveis ao desempenho.
Fique atento ao desempenho do banco de dados durante a fase de design e construção do banco de dados e do aplicativo. Especialmente, o consumo de recursos, o nível de isolamento e o comprimento do caminho da transação devem ser avaliados para cada consulta. Cada consulta e transação deve ser o mais leves possíveis. Uma boa disciplina de gerenciamento de conexão deve ser exercida, sem isso, o aplicativo pode parecer ter um desempenho aceitável com um número baixo de usuários, mas o desempenho pode diminuir significativamente à medida que o número de usuários aumenta.
Com o design adequado de aplicativo e consulta, o SQL Server é capaz de dar suporte a muitos milhares de usuários simultâneos em um único servidor, com pouco bloqueio.
Solucionar problemas de bloqueio
Independente de qual situação de bloqueio estamos, a metodologia para solucionar problemas de bloqueio é a mesma. Essas separações lógicas são o que determinará o restante da composição deste artigo. O conceito é localizar o bloqueador de cabeçalho e identificar o que a consulta está fazendo e por que está bloqueando. Depois que a consulta problemática é identificada (ou seja, o que está mantendo bloqueios por um período prolongado), a próxima etapa é analisar e determinar por que o bloqueio está acontecendo. Depois de entendermos o motivo, podemos fazer alterações reformulando a consulta e a transação.
Etapas na solução de problemas:
Identificar a sessão de bloqueio principal (bloqueador de cabeçalho)
Localizar a consulta e a transação que está causando o bloqueio (o que está mantendo os bloqueios por um longo período)
Analisar/entender por que o bloqueio prolongado ocorre
Resolver o problema de bloqueio remodelando a consulta e a transação
Agora, vamos discutir melhor sobre como identificar a principal sessão de bloqueio com uma captura de dados apropriada.
Coletar informações de bloqueio
Para combater a dificuldade de solucionar problemas de bloqueio, um administrador de banco de dados pode usar scripts SQL que monitoram constantemente o estado de travamento e bloqueio em SQL Server. Para coletar esses dados, há dois métodos complementares.
A primeira é consultar os DMOs (objetos de gerenciamento dinâmico) e armazenar os resultados para comparar ao longo do tempo. Alguns objetos referenciados neste artigo são as DMVs (exibição de gerenciamento dinâmico) e alguns são as DMFs (função de gerenciamento dinâmico).
A segunda é usar XEvents (Eventos Estendidos) ou Rastreamentos do SQL Profiler para capturar o que está em execução. Como o Rastreamento SQL o SQL Server Profiler foram preteridos, este guia de solução de problemas se concentrará em XEvents.
Coletar informações das DMVs
Referenciar DMVs para solucionar problemas de bloqueio tem o objetivo de identificar a SPID (ID de sessão) no início da cadeia de bloqueios e na instrução do SQL. Procure as SPIDs de vítima que estão sendo bloqueadas. Se uma SPID estiver sendo bloqueada por outra SPID, investigue a SPID que possui o recurso (a SPID de bloqueio). O proprietário da SPID também está sendo bloqueado? Você pode percorrer a cadeia para localizar o bloqueador de cabeçalho e investigar por que ele está mantendo seu bloqueio.
Para fazer isso, use um dos seguintes métodos:
No Pesquisador de Objetos SSMS (SQL Server Management Studio), clique com o botão direito do mouse no objeto de servidor de nível superior, expanda Relatórios, expanda Relatórios Padrão e, em seguida, selecione Atividades – Todas as Transações de Bloqueio. Este relatório mostra as transações atuais no cabeçalho de uma cadeia de bloqueio. Se você expandir a transação, o relatório mostrará as transações bloqueadas pela transação principal. Esse relatório também mostrará o Bloqueio da Instrução SQL e a Instrução SQL Bloqueada.
Abra o Monitor de Atividade no SSMS e consulte a coluna Bloqueado Por. Encontre mais informações sobre o Monitor de Atividades aqui.
Métodos mais detalhados baseados em consulta também estão disponíveis usando DMVs:
Os comandos
sp_who
esp_who2
são comandos mais antigos para mostrar todas as sessões atuais. A DMVsys.dm_exec_sessions
retorna mais dados em um conjunto de resultados que é mais fácil de ser consultado e filtrado. Você encontrarásys.dm_exec_sessions
no centro de outras consultas.Se já tiver uma sessão específica identificada, você poderá usar
DBCC INPUTBUFFER(<session_id>)
para localizar a última instrução que foi enviada por uma sessão. Resultados semelhantes podem ser retornados com a DMF (função de gerenciamento dinâmico)sys.dm_exec_input_buffer
em um conjunto de resultados que é mais fácil de ser consultado e filtrado, fornecendo a session_id e a request_id. Por exemplo, para retornar a consulta mais recente enviada por session_id 66 e request_id 0:
SELECT * FROM sys.dm_exec_input_buffer (66,0);
Consulte
sys.dm_exec_requests
e referencie a colunablocking_session_id
. Quandoblocking_session_id
= 0, uma sessão não está sendo bloqueada. Emborasys.dm_exec_requests
apenas as solicitações em execução no momento, toda conexão (ativa ou não) será listada emsys.dm_exec_sessions
. Estenda esta junção comum entresys.dm_exec_requests
esys.dm_exec_sessions
na próxima consulta. Lembre-se de que para ser retornada porsys.dm_exec_requests
, a consulta deve estar sendo executada ativamente com SQL Server.Execute esta consulta de exemplo para localizar as consultas em execução ativamente e o texto do lote SQL atual ou o texto do buffer de entrada, usando as DMVs sys.dm_exec_sql_text ou sys.dm_exec_input_buffer. Se os dados retornados pela coluna
text
desys.dm_exec_sql_text
for NULL, a consulta não estará em execução no momento. Nesse caso, a colunaevent_info
desys.dm_exec_input_buffer
conterá a última cadeia de caracteres de comando passada para o mecanismo SQL. Essa consulta também pode ser usada para identificar sessões que bloqueiam outras sessões, incluindo uma lista de session_ids bloqueadas por session_id.
WITH cteBL (session_id, blocking_these) AS
(SELECT s.session_id, blocking_these = x.blocking_these FROM sys.dm_exec_sessions s
CROSS APPLY (SELECT isnull(convert(varchar(6), er.session_id),'') + ', '
FROM sys.dm_exec_requests as er
WHERE er.blocking_session_id = isnull(s.session_id ,0)
AND er.blocking_session_id <> 0
FOR XML PATH('') ) AS x (blocking_these)
)
SELECT s.session_id, blocked_by = r.blocking_session_id, bl.blocking_these
, batch_text = t.text, input_buffer = ib.event_info, *
FROM sys.dm_exec_sessions s
LEFT OUTER JOIN sys.dm_exec_requests r on r.session_id = s.session_id
INNER JOIN cteBL as bl on s.session_id = bl.session_id
OUTER APPLY sys.dm_exec_sql_text (r.sql_handle) t
OUTER APPLY sys.dm_exec_input_buffer(s.session_id, NULL) AS ib
WHERE blocking_these is not null or r.blocking_session_id > 0
ORDER BY len(bl.blocking_these) desc, r.blocking_session_id desc, r.session_id;
- Execute esta consulta de exemplo elaborada, fornecida pelo Suporte da Microsoft, para identificar o cabeçalho de uma cadeia de bloqueio de sessão múltipla, incluindo o texto de consulta das sessões envolvidas em uma cadeia de bloqueio.
WITH cteHead ( session_id,request_id,wait_type,wait_resource,last_wait_type,is_user_process,request_cpu_time
,request_logical_reads,request_reads,request_writes,wait_time,blocking_session_id,memory_usage
,session_cpu_time,session_reads,session_writes,session_logical_reads
,percent_complete,est_completion_time,request_start_time,request_status,command
,plan_handle,sql_handle,statement_start_offset,statement_end_offset,most_recent_sql_handle
,session_status,group_id,query_hash,query_plan_hash)
AS ( SELECT sess.session_id, req.request_id, LEFT (ISNULL (req.wait_type, ''), 50) AS 'wait_type'
, LEFT (ISNULL (req.wait_resource, ''), 40) AS 'wait_resource', LEFT (req.last_wait_type, 50) AS 'last_wait_type'
, sess.is_user_process, req.cpu_time AS 'request_cpu_time', req.logical_reads AS 'request_logical_reads'
, req.reads AS 'request_reads', req.writes AS 'request_writes', req.wait_time, req.blocking_session_id,sess.memory_usage
, sess.cpu_time AS 'session_cpu_time', sess.reads AS 'session_reads', sess.writes AS 'session_writes', sess.logical_reads AS 'session_logical_reads'
, CONVERT (decimal(5,2), req.percent_complete) AS 'percent_complete', req.estimated_completion_time AS 'est_completion_time'
, req.start_time AS 'request_start_time', LEFT (req.status, 15) AS 'request_status', req.command
, req.plan_handle, req.[sql_handle], req.statement_start_offset, req.statement_end_offset, conn.most_recent_sql_handle
, LEFT (sess.status, 15) AS 'session_status', sess.group_id, req.query_hash, req.query_plan_hash
FROM sys.dm_exec_sessions AS sess
LEFT OUTER JOIN sys.dm_exec_requests AS req ON sess.session_id = req.session_id
LEFT OUTER JOIN sys.dm_exec_connections AS conn on conn.session_id = sess.session_id
)
, cteBlockingHierarchy (head_blocker_session_id, session_id, blocking_session_id, wait_type, wait_duration_ms,
wait_resource, statement_start_offset, statement_end_offset, plan_handle, sql_handle, most_recent_sql_handle, [Level])
AS ( SELECT head.session_id AS head_blocker_session_id, head.session_id AS session_id, head.blocking_session_id
, head.wait_type, head.wait_time, head.wait_resource, head.statement_start_offset, head.statement_end_offset
, head.plan_handle, head.sql_handle, head.most_recent_sql_handle, 0 AS [Level]
FROM cteHead AS head
WHERE (head.blocking_session_id IS NULL OR head.blocking_session_id = 0)
AND head.session_id IN (SELECT DISTINCT blocking_session_id FROM cteHead WHERE blocking_session_id != 0)
UNION ALL
SELECT h.head_blocker_session_id, blocked.session_id, blocked.blocking_session_id, blocked.wait_type,
blocked.wait_time, blocked.wait_resource, h.statement_start_offset, h.statement_end_offset,
h.plan_handle, h.sql_handle, h.most_recent_sql_handle, [Level] + 1
FROM cteHead AS blocked
INNER JOIN cteBlockingHierarchy AS h ON h.session_id = blocked.blocking_session_id and h.session_id!=blocked.session_id --avoid infinite recursion for latch type of blocking
WHERE h.wait_type COLLATE Latin1_General_BIN NOT IN ('EXCHANGE', 'CXPACKET') or h.wait_type is null
)
SELECT bh.*, txt.text AS blocker_query_or_most_recent_query
FROM cteBlockingHierarchy AS bh
OUTER APPLY sys.dm_exec_sql_text (ISNULL ([sql_handle], most_recent_sql_handle)) AS txt;
- Para capturar transações de execução longa ou não confirmadas, use outro conjunto de DMVs para exibir transações abertas atuais, incluindo sys.dm_tran_database_transactions, sys.dm_tran_session_transactions, sys.dm_exec_connections e
sys.dm_exec_sql_text
. Existem várias DMVs associadas à transações de rastreamento, consulte mais transações em DMVs aqui.
SELECT [s_tst].[session_id],
[database_name] = DB_NAME (s_tdt.database_id),
[s_tdt].[database_transaction_begin_time],
[sql_text] = [s_est].[text]
FROM sys.dm_tran_database_transactions [s_tdt]
INNER JOIN sys.dm_tran_session_transactions [s_tst] ON [s_tst].[transaction_id] = [s_tdt].[transaction_id]
INNER JOIN sys.dm_exec_connections [s_ec] ON [s_ec].[session_id] = [s_tst].[session_id]
CROSS APPLY sys.dm_exec_sql_text ([s_ec].[most_recent_sql_handle]) AS [s_est];
- Referência sys.dm_os_waiting_tasks que está na camada de thread/tarefa do SQL Server. Isso retorna informações sobre qual SQL wait_type a solicitação está enfrentando no momento. Assim como
sys.dm_exec_requests
, apenas as solicitações ativas são retornadas porsys.dm_os_waiting_tasks
.
Observação
Para obter muito mais sobre os tipos de espera, incluindo estatísticas de espera agregadas ao longo do tempo, consulte a DMV sys.dm_db_wait_stats.
- Use a DMV sys.dm_tran_locks para obter informações mais granulares sobre quais bloqueios foram colocados por consultas. Essa DMV pode retornar grandes quantidades de dados em uma instância de SQL Server de produção e é útil para diagnosticar quais bloqueios são mantidos no momento.
Devido à INNER JOIN em sys.dm_os_waiting_tasks
, a seguinte consulta restringe a saída de sys.dm_tran_locks
somente para as solicitações atualmente bloqueadas, o status de espera e os bloqueios delas:
SELECT table_name = schema_name(o.schema_id) + '.' + o.name
, wt.wait_duration_ms, wt.wait_type, wt.blocking_session_id, wt.resource_description
, tm.resource_type, tm.request_status, tm.request_mode, tm.request_session_id
FROM sys.dm_tran_locks AS tm
INNER JOIN sys.dm_os_waiting_tasks as wt ON tm.lock_owner_address = wt.resource_address
LEFT OUTER JOIN sys.partitions AS p on p.hobt_id = tm.resource_associated_entity_id
LEFT OUTER JOIN sys.objects o on o.object_id = p.object_id or tm.resource_associated_entity_id = o.object_id
WHERE resource_database_id = DB_ID()
AND object_name(p.object_id) = '<table_name>';
Com as DMVs, armazenar os resultados da consulta ao longo do tempo fornecerá pontos de dados que permitirão que você revise o bloqueio em um intervalo de tempo especificado para identificar as tendências ou o bloqueio persistente. A ferramenta de acesso para CSS para solucionar esses problemas é usar o coletor de dados PSSDiag. Essa ferramenta usa as "Estatísticas de Desempenho do SQL Server" para coletar conjuntos de resultados das DMVs referenciadas acima, ao longo do tempo. Como essa ferramenta está em constante evolução, examine a versão pública mais recente do DiagManager no GitHub.
Coletar informações de eventos estendidos
Além das informações acima, geralmente é necessário capturar um rastreamento das atividades no servidor para investigar completamente um problema de bloqueio no SQL Server. Por exemplo, se uma sessão executar várias instruções em uma transação, somente a última instrução que foi enviada será representada. No entanto, uma das instruções anteriores pode ser o motivo pelo qual os bloqueios ainda estão sendo mantidos. Um rastreamento permitirá que você veja todos os comandos executados por uma sessão dentro da transação atual.
Há duas maneiras de capturar rastreamentos no SQL Server; Eventos Estendidos (XEvents) e Rastreamentos do Criador de Perfil. No entanto, os rastreamentos SQL usando o Criador de Perfil do SQL foram preteridos. O XEvents é a plataforma de rastreamento mais recente e superior que permite mais versatilidade e menos impacto para o sistema observado, e sua interface é integrada ao SSMS.
Há sessões de Evento Estendido pré-criadas prontas para iniciar no SSMS, listadas Pesquisador de Objetos no menu do XEvent Profiler. Para obter mais informações, consulte XEvent Profiler. Você também pode criar suas próprias sessões de Evento Estendido personalizadas no SSMS, consulte Assistente de Nova Sessão de Eventos Estendidos. Para solucionar problemas de bloqueio, normalmente capturaremos:
- Erros de categoria:
- Atenção
- Blocked_process_report**
- Error_reported (Administrador do Canal)
- Exchange_spill
- Execution_warning
**Para configurar o limite e a frequência em que os relatórios de processo bloqueados são gerados, use o comando sp_configure para configurar a opção de limite de processo bloqueado, que pode ser definida em segundos. Por padrão, não são produzidos relatórios de processo bloqueado.
Avisos de Categoria:
- Hash_warning
- Missing_column_statistics
- Missing_join_predicate
- Sort_warning
Execução da Categoria:
- Rpc_completed
- Rpc_starting
- Sql_batch_completed
- Sql_batch_starting
Bloqueio de Categoria
- Lock_deadlock
Sessão de Categoria
- Existing_connection
- Logon
- Logout
Identificar e resolver cenários de bloqueio comuns
Ao examinar as informações acima, você pode determinar a causa da maioria dos problemas de bloqueio. O restante deste artigo é uma discussão de como usar essas informações para identificar e resolver alguns cenários comuns de bloqueio. Esta discussão pressupõe que você tenha usado os scripts de bloqueio (referenciados anteriormente) para capturar informações sobre os SPIDs de bloqueio e ter capturado a atividade do aplicativo usando uma sessão XEvent.
Analisar dados de bloqueio
Examine a saída das DMVs
sys.dm_exec_requests
esys.dm_exec_sessions
para determinar os cabeçalhos das cadeias de bloqueio usandoblocking_these
esession_id
. Isso identificará de forma mais clara quais solicitações são bloqueadas e quais estão bloqueando. Entenda mais sobre as sessões bloqueadas e bloqueando. Existe uma raiz ou comum para a cadeia de bloqueio? Elas provavelmente compartilham uma tabela comum e uma ou mais sessões envolvidas em uma cadeia de bloqueio está executando uma operação de gravação.Examine a saída das DMVs
sys.dm_exec_requests
esys.dm_exec_sessions
para obter informações sobre as SPIDs no cabeçalho da cadeia de bloqueio. Procure as seguintes colunas:sys.dm_exec_requests.status
Esta coluna mostra o status de uma solicitação específica. Normalmente, um status de suspensão indica que a SPID concluiu a execução e está aguardando o aplicativo enviar outra consulta ou lote. Um status executável ou em execução indica que a SPID está processando uma consulta no momento. A tabela a seguir fornece breves explicações dos vários valores de status.
Status Significado Segundo plano A SPID está executando uma tarefa em segundo plano, como detecção de deadlock, gravador de log ou ponto de verificação. Hibernando A SPID não está em execução no momento. Isso geralmente indica que a SPID está aguardando um comando do aplicativo. Executando A SPID está sendo executado no momento em um agendador. Executável A SPID está na fila executável de um agendador e aguardando para obter o tempo do agendador. Suspenso A SPID está aguardando um recurso, como um bloqueio ou uma trava. sys.dm_exec_sessions.open_transaction_count
Esta coluna informa o número de transações abertas nesta sessão. Se esse valor for maior do que 0, a SPID estará dentro de uma transação aberta e poderá estar mantendo os bloqueios adquiridos por uma instrução dentro da transação. A transação aberta pode ter sido criada por uma instrução ativa no momento ou por uma solicitação de declaração que foi executada no passado e não está mais ativa.
sys.dm_exec_requests.open_transaction_count
Da mesma forma, essa coluna informa o número de transações abertas nesta solicitação. Se esse valor for maior que 0, o SPID estará dentro de uma transação aberta e poderá estar mantendo bloqueios adquiridos por qualquer instrução ativa dentro da transação. Ao contrário
sys.dm_exec_sessions.open_transaction_count
de , se não houver uma solicitação ativa, esta coluna mostrará 0.sys.dm_exec_requests.wait_type
,wait_time
elast_wait_type
Se o
sys.dm_exec_requests.wait_type
for NULL, a solicitação não está aguardando nada e o valorlast_wait_type
indica o últimowait_type
que a solicitação encontrou. Para obter mais informações sobre osys.dm_os_wait_stats
e uma descrição dos tipos de espera mais comuns, consulte sys.dm_os_wait_stats. O valorwait_time
pode ser usado para determinar se a solicitação está progredindo. Quando uma consulta na tabelasys.dm_exec_requests
devolve um valor na colunawait_time
que é menor do que o valorwait_time
de uma consulta anterior desys.dm_exec_requests
, isso indica que o bloqueio anterior foi adquirido e liberado e agora está aguardando um novo bloqueio (supondo que owait_time
seja diferente de zero). Isso pode ser verificado por meio da comparação dewait_resource
entre a saídasys.dm_exec_requests
, que exibe o recurso que a solicitação está aguardando.sys.dm_exec_requests.wait_resource
Essa coluna indica o recurso que uma solicitação bloqueada está aguardando. A tabela a seguir lista formatos comuns
wait_resource
e seus significados:Recurso Formato Exemplo Explicação Tabela DatabaseID:ObjectID:IndexID TAB: 5:261575970:1 Nesse caso, a ID do banco de dados 5 é o banco de dados de exemplo pubs, object_id
261575970 é a tabela de títulos e 1 é o índice clusterizado.Página DatabaseID:FileID:PageID PAGE: 5:1:104 Nesse caso, a ID do banco de dados 5 é pubs, a ID do arquivo de dados primário 1 e a página 104 é uma página que pertence à tabela de títulos. Para identificar a object_id à qual a página pertence, use a função de gerenciamento dinâmico sys.dm_db_page_info, passando o DatabaseID, o FileID, o PageId do wait_resource
.Chave DatabaseID: Hobt_id (valor de hash para chave de índice) KEY: 5:72057594044284928 (3300a4f361aa) Nesse caso, a ID de banco de dados 5 é pubs, Hobt_ID 72057594044284928 corresponde a index_id 2 para object_id 261575970 (tabela de títulos). Use a exibição de catálogo sys.partitions
para associar ohobt_id
a um determinadoindex_id
eobject_id
. Não é possível remover o hash do hash de chave de índice para um valor de chave específico.Linha DatabaseID:FileID:PageID:Slot(row) RID: 5:1:104:3 Nesse caso, a ID de banco de dados 5 é pubs, a ID do arquivo de dados primário 1, a página 104 é uma página que pertence à tabela de títulos e o slot 3 indica a posição da linha na página. Compilar DatabaseID:FileID:PageID:Slot(row) RID: 5:1:104:3 Nesse caso, a ID de banco de dados 5 é pubs, a ID do arquivo de dados primário 1, a página 104 é uma página que pertence à tabela de títulos e o slot 3 indica a posição da linha na página. A
sys.dm_tran_active_transactions
DMV sys.dm_tran_active_transactions contém dados sobre transações abertas que podem ser unidas a outras DMVs para obter uma imagem completa das transações que estão aguardando confirmação ou reversão. Use a consulta a seguir para devolver informações sobre transações abertas, unidas a outras DMVs, incluindo sys.dm_tran_session_transactions. Considere o estado atual de uma transação,transaction_begin_time
e outros dados de situação para avaliar se ela pode ser uma fonte de bloqueio.SELECT tst.session_id, [database_name] = db_name(s.database_id) , tat.transaction_begin_time , transaction_duration_s = datediff(s, tat.transaction_begin_time, sysdatetime()) , transaction_type = CASE tat.transaction_type WHEN 1 THEN 'Read/write transaction' WHEN 2 THEN 'Read-only transaction' WHEN 3 THEN 'System transaction' WHEN 4 THEN 'Distributed transaction' END , input_buffer = ib.event_info, tat.transaction_uow , transaction_state = CASE tat.transaction_state WHEN 0 THEN 'The transaction has not been completely initialized yet.' WHEN 1 THEN 'The transaction has been initialized but has not started.' WHEN 2 THEN 'The transaction is active - has not been committed or rolled back.' WHEN 3 THEN 'The transaction has ended. This is used for read-only transactions.' WHEN 4 THEN 'The commit process has been initiated on the distributed transaction.' WHEN 5 THEN 'The transaction is in a prepared state and waiting resolution.' WHEN 6 THEN 'The transaction has been committed.' WHEN 7 THEN 'The transaction is being rolled back.' WHEN 8 THEN 'The transaction has been rolled back.' END , transaction_name = tat.name, request_status = r.status , tst.is_user_transaction, tst.is_local , session_open_transaction_count = tst.open_transaction_count , s.host_name, s.program_name, s.client_interface_name, s.login_name, s.is_user_process FROM sys.dm_tran_active_transactions tat INNER JOIN sys.dm_tran_session_transactions tst on tat.transaction_id = tst.transaction_id INNER JOIN Sys.dm_exec_sessions s on s.session_id = tst.session_id LEFT OUTER JOIN sys.dm_exec_requests r on r.session_id = s.session_id CROSS APPLY sys.dm_exec_input_buffer(s.session_id, null) AS ib;
Outras colunas
As colunas restantes em sys.dm_exec_sessions e sys.dm_exec_request também podem fornecer informações sobre a raiz de um problema. Sua utilidade varia dependendo das circunstâncias do problema. Por exemplo, você pode determinar se o problema ocorre somente com determinados clientes (
hostname
), em determinadas bibliotecas de rede (client_interface_name
), quando o último lote enviado por um SPID eralast_request_start_time
emsys.dm_exec_sessions
, por quanto tempo uma solicitação estava em execução usadostart_time
emsys.dm_exec_requests
e assim por diante.
Cenários comuns de bloqueio
A tabela a seguir mapeia sintomas comuns a suas causas prováveis.
As colunas wait_type
, open_transaction_count
e status
se referem às informações retornadas por sys.dm_exec_request, outras colunas podem ser retornadas por sys.dm_exec_sessions. A coluna "Isso resolve?" indica se o bloqueio será resolvido ou não resolvido sozinho ou se a sessão deve ser eliminada por meio do comando KILL
. Para obter mais informações, confira KILL (Transact-SQL).
Cenário | Wait_type | Open_Tran | Status | Resolvido? | Outros sintomas |
---|---|---|---|---|---|
1 | NOT NULL | >= 0 | executável | Sim, quando a consulta é concluída. | Em sys.dm_exec_sessions , as colunas reads , cpu_time e/ou memory_usage aumentarão com o tempo. A duração da consulta será alta quando for concluída. |
2 | NULO | >0 | hibernando | Não, mas a SPID pode ser eliminada. | Um sinal de atenção pode ser visto na sessão de Evento Estendido para esse SPID, indicando que ocorreu um tempo limite ou cancelamento de consulta. |
3 | NULO | >= 0 | executável | Não. Não será resolvido até que o cliente busque todas as linhas ou feche a conexão. A SPID pode ser eliminada, mas isso pode levar até 30 segundos. | Se open_transaction_count = 0 e o SPID manter bloqueios enquanto o nível de isolamento da transação for padrão (LER CONFIRMADOS), essa é uma causa provável. |
4 | Varia | >= 0 | executável | Não. Não será resolvido até que o cliente cancele consultas ou feche conexões. As SPIDs podem ser eliminadas, mas pode levar até 30 segundos. | A coluna hostname em sys.dm_exec_sessions para a SPID no cabeçalho de uma cadeia de bloqueio será a mesma que uma das SPIDs que ela está bloqueando. |
5 | NULO | >0 | reversão | Sim. | Um sinal de atenção pode ser visto na sessão de Eventos Estendidos para esse SPID, indicando que um tempo limite de consulta ou cancelamento ocorreu ou simplesmente uma instrução de reversão foi emitida. |
6 | NULO | >0 | hibernando | Enfim. Quando o Windows NT determina que a sessão não está mais ativa, a conexão é interrompida. | O valor de last_request_start_time em sys.dm_exec_sessions é muito anterior à hora atual. |
Cenários de bloqueio detalhados
Cenário 1: bloqueio causado por uma consulta normalmente em execução com um longo tempo de execução
Nesse cenário, uma consulta em execução ativamente adquiriu bloqueios e os bloqueios não são liberados (ela é afetada pelo nível de isolamento da transação). Portanto, outras sessões aguardarão os bloqueios até que sejam liberados.
Resolução:
A solução para esse tipo de problema de bloqueio é procurar maneiras de otimizar a consulta. Essa classe de problema de bloqueio pode ser um problema de desempenho e exigir que você o busque como tal. Para obter informações sobre como solucionar problemas de uma consulta de execução lenta específica, consulte Como solucionar problemas de consultas de execução lenta no SQL Server. Para obter mais informações, confira Monitorar e ajustar o desempenho.
Os relatórios internos para SSMS do Repositório de Consultas (introduzidos no SQL Server 2016) também são uma ferramenta altamente recomendada e valiosa para identificar as consultas mais dispendidas e planos de execução abaixo do ideal.
Se você tiver uma consulta de execução longa que está bloqueando outros usuários e não pode ser otimizada, considere movê-la de um ambiente OLTP para um sistema de relatórios dedicado. Você também pode usar grupos de disponibilidade Always On para sincronizar uma réplica somente leitura do banco de dados.
Observação
O bloqueio durante a execução da consulta pode ser causado pelo escalonamento de consulta, um cenário em que os bloqueios de linha ou página são escalonados para bloqueios de tabela. O Microsoft SQL Server determina dinamicamente quando executar o escalonamento de bloqueio. A maneira mais simples e segura de evitar o escalonamento de bloqueios é manter as transações curtas e reduzir o volume de bloqueio de consultas caras para que os limites de escalonamento de bloqueio não sejam excedidos. Para obter mais informações sobre como detectar e evitar o escalonamento excessivo de bloqueios, consulte Resolver o problema de bloqueio causado pelo escalonamento de bloqueios.
Cenário 2: bloqueio causado por um SPID em suspensão que tem uma transação não confirmada
Esse tipo de bloqueio geralmente pode ser identificado por um SPID que está em suspensão ou aguardando um comando, mas cujo nível de aninhamento de transação (@@TRANCOUNT
, open_transaction_count
de sys.dm_exec_requests
) é maior que zero. Essa situação pode ocorrer se o aplicativo tiver um tempo limite de consulta ou emitir um cancelamento sem emitir o número necessário de instruções REVERTER e/ou CONFIRMAR. Quando um SPID receber um tempo limite de consulta ou um cancelamento, ele encerrará a consulta atual e o lote, mas não reverterá ou confirmará automaticamente a transação. O aplicativo é responsável por isso, pois O SQL Server não pode supor que uma transação inteira deve ser revertida devido a uma única consulta ser cancelada. O tempo limite ou cancelamento da consulta será exibido como um evento de sinal de ATENÇÃO para o SPID na sessão de Evento Estendido.
Para demonstrar uma transação explícita não confirmada, emita a seguinte consulta:
CREATE TABLE #test (col1 INT);
INSERT INTO #test SELECT 1;
GO
BEGIN TRAN
UPDATE #test SET col1 = 2 where col1 = 1;
Em seguida, execute essa consulta na mesma janela:
SELECT @@TRANCOUNT;
ROLLBACK TRAN
DROP TABLE #test;
A saída da segunda consulta indica que o nível de aninhamento de transação é um. Todos os bloqueios adquiridos na transação ainda serão mantidos até que a transação seja confirmada ou revertida. Se os aplicativos abrirem e confirmarem explicitamente as transações, uma comunicação ou outro erro poderá deixar a sessão e sua transação em um estado aberto.
Use o script descrito anteriormente neste artigo baseado em sys.dm_tran_active_transactions
para identificar as transações não confirmadas no momento em toda a instância.
Resoluções:
Além disso, essa classe de problema de bloqueio pode ser um problema de desempenho e exigir que você o busque como tal. Se o tempo de execução da consulta puder ser reduzido, o tempo limite ou o cancelamento da consulta pode não ocorrer. É importante que o aplicativo possa lidar com o tempo limite ou cancelar cenários caso eles surjam, mas você também pode se beneficiar da análise do desempenho da consulta.
Os aplicativos devem gerenciar corretamente os níveis de aninhamento de transações ou podem causar um problema de bloqueio após o cancelamento da consulta dessa maneira. Considere o seguinte:
No manipulador de erros do aplicativo cliente, execute
IF @@TRANCOUNT > 0 ROLLBACK TRAN
após um erro, mesmo que o aplicativo cliente não acredite que uma transação está aberta. A verificação de transações abertas é necessária, pois um procedimento armazenado chamado durante o lote pode ter iniciado uma transação sem o conhecimento do aplicativo cliente. Determinadas condições, como cancelar a consulta, impedem que o procedimento seja executado após a instrução atual, portanto, mesmo que o procedimento tenha lógica para verificarIF @@ERROR <> 0
e anular a transação, esse código de reversão não será executado nesses casos.Se o pool de conexões estiver sendo usado em um aplicativo que abre a conexão e executa algumas consultas antes de liberar a conexão de volta para o pool, como um aplicativo baseado na Web, desabilitar temporariamente o pool de conexões pode ajudar a aliviar o problema até que o aplicativo cliente seja modificado para lidar com os erros adequadamente. Ao desabilitar o pool de conexões, a liberação da conexão causará uma desconexão física da conexão SQL Server, fazendo com que o servidor reverta as transações abertas.
Use
SET XACT_ABORT ON
para a conexão ou em qualquer procedimento armazenado que inicie transações e não esteja limpando após um erro. No caso de um erro de tempo de execução, essa configuração anulará todas as transações abertas e devolverá o controle para o cliente. Para obter mais informações, revise SET XACT_ABORT (Transact-SQL).
Observação
A conexão não é redefinida até que seja reutilizada do pool de conexões, portanto, é possível que um usuário possa abrir uma transação e liberar a conexão com o pool de conexões, mas ela pode não ser reutilizada por vários segundos, durante o qual a transação permaneceria aberta. Se a conexão não for reutilizada, a transação será anulada quando a conexão expirar e for removida do pool de conexões. Portanto, é ideal que o aplicativo cliente anule transações em seu manipulador de erros ou use SET XACT_ABORT ON
para evitar esse possível atraso.
Cuidado
Após SET XACT_ABORT ON
, as instruções T-SQL após uma instrução que causa um erro não serão executadas. Isso pode afetar o fluxo pretendido de código existente.
Cenário 3: bloqueio causado por um SPID cujo aplicativo cliente correspondente não busca todas as linhas de resultado até a conclusão
Depois de enviar uma consulta para o servidor, todos os aplicativos devem buscar imediatamente todas as linhas de resultado para conclusão. Se um aplicativo não buscar todas as linhas de resultado, os bloqueios poderão ser deixados nas tabelas, bloqueando outros usuários. Se você estiver usando um aplicativo que envia de forma transparente instruções SQL para o servidor, o aplicativo deve buscar todas as linhas de resultado. Se isso não ocorrer (e se ele não puder ser configurado para fazer isso), talvez você não consiga resolver o problema de bloqueio. Para evitar o problema, você pode restringir o mau desempenho de aplicativos a um relatório ou a um banco de dados de suporte a decisões, separado do banco de dados OLTP principal.
Resolução:
O aplicativo deve ser reescrito para buscar todas as linhas do resultado até a conclusão. Isso não exclui o uso de OFFSET e FETCH na cláusula ORDER BY de uma consulta para executar a paginação do lado do servidor.
Cenário 4: bloqueio causado por um deadlock de cliente/servidor distribuído
Ao contrário de um deadlock convencional, um deadlock distribuído não é detectável usando o gerenciador de bloqueioS RDBMS. Isso ocorre porque apenas um dos recursos envolvidos no deadlock é um bloqueio de SQL Server. O outro lado do deadlock está no nível do aplicativo cliente, sobre o qual SQL Server não tem controle. As duas seções a seguir mostram exemplos exemplos de como isso pode acontecer e possíveis maneiras pelas quais o aplicativo pode evitá-lo.
Exemplo A: deadlock distribuído de cliente/Servidor com um único thread de cliente
Se o cliente tiver várias conexões abertas e um único thread de execução, o deadlock distribuído a seguir poderá ocorrer. Observação, o termo dbproc
usado aqui refere-se à estrutura de conexão do cliente.
SPID1------blocked on lock------->SPID2
/\ (waiting to write results back to client)
|
| |
| | Server side
| ================================|==================================
| <-- single thread --> | Client side
| \/
dbproc1 <------------------- dbproc2
(waiting to fetch (effectively blocked on dbproc1, awaiting
next row) single thread of execution to run)
No caso mostrado acima, um único thread de aplicativo cliente tem duas conexões abertas. Ele envia de forma assíncrona uma operação SQL no dbproc1. Isso significa que ele não aguarda o retorno da chamada antes de continuar. Em seguida, o aplicativo envia outra operação SQL no dbproc2 e aguarda os resultados para iniciar o processamento dos dados retornados. Quando os dados começam a voltar (o que o dbproc responde pela primeira vez– suponha que seja dbproc1), eles processam para concluir todos os dados retornados nesse dbproc. Ele busca resultados de dbproc1 até que o SPID1 seja bloqueado em um bloqueio mantido pelo SPID2 (porque as duas consultas estão sendo executadas de forma assíncrona no servidor). Neste ponto, dbproc1 aguardará indefinidamente por mais dados. O SPID2 não está bloqueado em uma trava, mas tenta enviar dados para seu cliente, dbproc2. No entanto, dbproc2 é efetivamente bloqueado no dbproc1 na camada do aplicativo, pois o único thread de execução para o aplicativo está em uso pelo dbproc1. Isso resulta em um deadlock que o SQL Server não pode detectar ou resolver porque apenas um dos recursos envolvidos é um recurso SQL Server.
Exemplo B: deadlock distribuído de cliente/servidor com um thread por conexão
Mesmo que exista um thread separado para cada conexão no cliente, uma variação desse deadlock distribuído ainda poderá ocorrer, conforme mostrado a seguir.
SPID1------blocked on lock-------->SPID2
/\ (waiting on net write) Server side
| |
| |
| INSERT |SELECT
| ================================|==================================
| <-- thread per dbproc --> | Client side
| \/
dbproc1 <-----data row------- dbproc2
(waiting on (blocked on dbproc1, waiting for it
insert) to read the row from its buffer)
Esse caso é semelhante ao Exemplo A, exceto que dbproc2 e SPID2 estão executando uma instrução SELECT
com a intenção de executar o processamento de linha por vez e entregar cada linha por meio de um buffer para dbproc1 para uma instrução INSERT
, UPDATE
ou DELETE
na mesma tabela. Eventualmente, o SPID1 (executando o INSERT
, UPDATE
ou DELETE
) torna-se bloqueado em uma trava mantida pelo SPID2 (executando o SELECT
). O SPID2 grava uma linha de resultado no dbproc2 do cliente. Dbproc2 tenta passar a linha em um buffer para dbproc1, mas encontra dbproc1 ocupado (está bloqueado aguardando o SPID1 concluir o INSERT
atual, que está bloqueado no SPID2). Neste ponto, dbproc2 é bloqueado na camada de aplicativo por dbproc1 cujo SPID (SPID1) está bloqueado no nível do banco de dados pelo SPID2. Novamente, isso resulta em um deadlock que o SQL Server não pode detectar ou resolver porque apenas um dos recursos envolvidos é um recurso do SQL Server.
Os dois exemplos A e B são problemas fundamentais que os desenvolvedores de aplicativos devem estar cientes. Eles devem codificar aplicativos para lidar com esses casos adequadamente.
Resolução:
Quando um tempo limite de consulta tiver sido fornecido, se o deadlock distribuído ocorrer, ele será interrompido quando o tempo limite ocorrer. Consulte a documentação do provedor de conexão para obter mais informações sobre como usar um tempo limite de consulta.
Cenário 5: bloqueio causado por uma sessão em um estado de reversão
Uma consulta de modificação de dados que é encerrada ou cancelada fora de uma transação definida pelo usuário será revertida. Isso também pode ocorrer como um efeito colateral da desconexão da sessão de rede do cliente ou quando uma solicitação é selecionada como a vítima do deadlock. Isso geralmente pode ser identificado observando a saída de sys.dm_exec_requests
, que pode indicar REVERSÃO command
, e a coluna percent_complete
pode mostrar o progresso.
Uma consulta de modificação de dados que é encerrada ou cancelada fora de uma transação definida pelo usuário será revertida. Isso também pode ocorrer como um efeito colateral da reinicialização do computador cliente e da desconexão da sessão de rede. Da mesma forma, uma consulta selecionada como a vítima de deadlock será revertida. Uma consulta de modificação de dados geralmente não pode ser revertida mais rapidamente do que as alterações aplicadas inicialmente. Por exemplo, se uma instrução DELETE
, INSERT
ou UPDATE
tivesse sido executada por uma hora, poderia levar pelo menos uma hora para ser revertida. Esse é o comportamento esperado, pois as alterações feitas devem ser revertidas ou a integridade transacional e física no banco de dados seria comprometida. Como isso deve acontecer, o SQL Server marca o SPID em um estado de ouro ou reversão (o que significa que ele não pode ser encerrado ou selecionado como uma vítima de deadlock). Isso geralmente pode ser identificado observando a saída de sp_who
, que pode indicar o comando REVERSÃO. A coluna status
de sys.dm_exec_sessions
indicará um status REVERSÃO.
Observação
Reversões longas são raras quando o recurso Recuperação Acelerada de Banco de Dados está habilitado. Esse recurso foi introduzido no SQL Server 2019.
Resolução:
Você deve aguardar até que a sessão termine de reverter as alterações feitas.
Se a instância for desligada no meio dessa operação, o banco de dados estará no modo de recuperação após a reinicialização e ficará inacessível até que todas as transações abertas sejam processadas. A recuperação de inicialização leva essencialmente a mesma quantidade de tempo por transação que a recuperação em tempo de execução, e o banco de dados fica inacessível durante esse período. Portanto, forçar o servidor a corrigir um SPID em um estado de reversão geralmente será contraproducente. No SQL Server 2019 com a Recuperação de Banco de Dados Acelerada habilitada, isso não deve ocorrer.
Para evitar essa situação, não execute operações de gravação em lote grandes ou operações de manutenção ou de criação de índice durante horário o comercial em sistemas OLTP. Se possível, execute essas operações durante períodos de atividade baixa.
Cenário 6: bloqueio causado por uma conexão órfã
Esse é um cenário de problema comum e se sobrepõe parcialmente ao Cenário 2. Se o aplicativo cliente parar, a estação de trabalho do cliente será reiniciada ou se houver um erro de anulação em lote, todos eles poderão deixar uma transação aberta. Essa situação poderá ocorrer se o aplicativo não reverter a transação nos blocos CATCH
ou FINALLY
no aplicativo ou se ele não lidar com essa situação de outra forma.
Nesse cenário, embora a execução de um lote SQL tenha sido cancelada, o aplicativo deixa a transação SQL aberta. Da perspectiva da instância do SQL Server, o cliente ainda parece estar presente e todos os bloqueios adquiridos são mantidos.
Para demonstrar uma transação órfã, execute a seguinte consulta, que simula um erro de anulação em lote inserindo dados em uma tabela inexistente:
CREATE TABLE #test2 (col1 INT);
INSERT INTO #test2 SELECT 1;
go
BEGIN TRAN
UPDATE #test2 SET col1 = 2 where col1 = 1;
INSERT INTO #NonExistentTable values (10)
Em seguida, execute essa consulta na mesma janela:
SELECT @@TRANCOUNT;
A saída da segunda consulta indica que o nível de aninhamento de transação é um. Todos os bloqueios adquiridos na transação ainda serão mantidos até que a transação seja confirmada ou revertida. Como o lote já foi anulado pela consulta, o aplicativo que o executa pode continuar executando outras consultas na mesma sessão sem limpar a transação que ainda está aberta. O bloqueio será mantido até que a sessão seja encerrada ou a instância do SQL Server seja reiniciada.
Resoluções:
- A melhor maneira de evitar essa condição é melhorar o tratamento de erros/exceções do aplicativo, especialmente para encerramentos inesperados. Certifique-se de usar um bloco
Try-Catch-Finally
no código do aplicativo e reverter a transação no caso de uma exceção. - Considere usar
SET XACT_ABORT ON
para a sessão ou em qualquer procedimento armazenado que inicie transações e não esteja limpando após um erro. No caso de um erro em tempo de execução que aborte o lote, essa configuração anulará todas as transações abertas e retornará o controle ao cliente. Para obter mais informações, revise SET XACT_ABORT (Transact-SQL). - Para resolver uma conexão órfã de um aplicativo cliente que foi desconectado sem limpar adequadamente seus recursos, você pode encerrar o SPID usando o comando
KILL
. Para referência, consulte ENCERRAR (Transact-SQL).
O comando KILL
usa o valor da SPID como entrada. Por exemplo, para encerrar o SPID 9, emita o seguinte comando:
KILL 99
Observação
O comando KILL
pode levar até 30 segundos para ser concluído, devido ao intervalo entre as verificações do comando KILL
.