Eu
A essência do problema é que você está tirando
S
trava nas linhas da tabela do sistema em tempdb
que pode entrar em conflito com os bloqueios necessários para tempdb
interno transações de limpeza. Quando este trabalho de limpeza é alocado para a mesma sessão que possui o
S
lock pode ocorrer um travamento indefinido. Para evitar este problema com certeza você precisa parar de referenciar o
system
objetos dentro de tempdb
. É possível criar uma tabela de números sem fazer referência a nenhuma tabela externa. O seguinte não precisa ler nenhuma linha da tabela base e, portanto, também não precisa de bloqueios.
WITH Ten(N) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)
SELECT TOP 1000000 IDENTITY(INT, 1, 1) Number
INTO Numbers
FROM Ten T10,
Ten T100,
Ten T1000,
Ten T10000,
Ten T100000,
Ten T1000000
Passos para reproduzir
Primeiro crie um procedimento
CREATE PROC P
AS
SET NOCOUNT ON;
DECLARE @T TABLE (X INT)
GO
Em seguida, reinicie o serviço SQL e em uma conexão execute
WHILE NOT EXISTS(SELECT *
FROM sys.dm_os_waiting_tasks
WHERE session_id = blocking_session_id)
BEGIN
/*This will cause the problematic droptemp transactions*/
EXEC sp_recompile 'P'
EXEC P
END;
SELECT *
FROM sys.dm_os_waiting_tasks
WHERE session_id = blocking_session_id
Então em outra conexão execute
USE tempdb;
SELECT TOP 1000000 IDENTITY(INT, 1, 1) Number
INTO #T
FROM sys.objects s1
CROSS JOIN sys.objects s2
CROSS JOIN sys.objects s3
CROSS JOIN sys.objects s4;
DROP TABLE #T
A consulta que preenche a tabela Numbers parece conseguir entrar em uma situação de bloqueio ativo com as transações internas do sistema que limpam objetos temporários, como variáveis de tabela.
Consegui bloquear o ID de sessão 53 dessa maneira. Está bloqueado indefinidamente. A saída de
sp_WhoIsActive
mostra que esse spid fica quase todo o tempo suspenso. Em execuções consecutivas os números no reads
coluna aumenta, mas os valores nas outras colunas permanecem praticamente os mesmos. A duração da espera não mostra um padrão crescente, embora indique que deve ser desbloqueado periodicamente antes de ser bloqueado novamente.
SELECT *
FROM sys.dm_os_waiting_tasks
WHERE session_id = blocking_session_id
Devoluções
+----------------------+------------+-----------------+------------------+-----------+--------------------+-----------------------+---------------------+--------------------------+--------------------------------------------------------------------------------------------------+
| waiting_task_address | session_id | exec_context_id | wait_duration_ms | wait_type | resource_address | blocking_task_address | blocking_session_id | blocking_exec_context_id | resource_description |
+----------------------+------------+-----------------+------------------+-----------+--------------------+-----------------------+---------------------+--------------------------+--------------------------------------------------------------------------------------------------+
| 0x00000002F2C170C8 | 53 | 0 | 86 | LCK_M_X | 0x00000002F9B13040 | 0x00000002F2C170C8 | 53 | NULL | keylock hobtid=281474978938880 dbid=2 id=lock2f9ac8880 mode=U associatedObjectId=281474978938880 |
+----------------------+------------+-----------------+------------------+-----------+--------------------+-----------------------+---------------------+--------------------------+--------------------------------------------------------------------------------------------------+
Usando o id na descrição do recurso
SELECT o.name
FROM sys.allocation_units au WITH (NOLOCK)
INNER JOIN sys.partitions p WITH (NOLOCK)
ON au.container_id = p.partition_id
INNER JOIN sys.all_objects o WITH (NOLOCK)
ON o.object_id = p.object_id
WHERE allocation_unit_id = 281474978938880
Devoluções
+------------+
| name |
+------------+
| sysschobjs |
+------------+
Corrida
SELECT resource_description,request_status
FROM sys.dm_tran_locks
WHERE request_session_id = 53 AND request_status <> 'GRANT'
Devoluções
+----------------------+----------------+
| resource_description | request_status |
+----------------------+----------------+
| (246708db8c1f) | CONVERT |
+----------------------+----------------+
Conectando via DAC e executando
SELECT id,name
FROM tempdb.sys.sysschobjs WITH (NOLOCK)
WHERE %%LOCKRES%% = '(246708db8c1f)'
Devoluções
+-------------+-----------+
| id | name |
+-------------+-----------+
| -1578606288 | #A1E86130 |
+-------------+-----------+
Curioso sobre o que é isso
SELECT name,user_type_id
FROM tempdb.sys.columns
WHERE object_id = -1578606288
Devoluções
+------+--------------+
| name | user_type_id |
+------+--------------+
| X | 56 |
+------+--------------+
Este é o nome da coluna na variável de tabela usada pelo proc armazenado.
Corrida
SELECT request_mode,
request_status,
request_session_id,
request_owner_id,
lock_owner_address,
t.transaction_id,
t.name,
t.transaction_begin_time
FROM sys.dm_tran_locks l
JOIN sys.dm_tran_active_transactions t
ON l.request_owner_id = t.transaction_id
WHERE resource_description = '(246708db8c1f)'
Devoluções
+--------------+----------------+--------------------+------------------+--------------------+----------------+-------------+-------------------------+
| request_mode | request_status | request_session_id | request_owner_id | lock_owner_address | transaction_id | name | transaction_begin_time |
+--------------+----------------+--------------------+------------------+--------------------+----------------+-------------+-------------------------+
| U | GRANT | 53 | 227647 | 0x00000002F1EF6800 | 227647 | droptemp | 2013-11-24 18:36:28.267 |
| S | GRANT | 53 | 191790 | 0x00000002F9B16380 | 191790 | SELECT INTO | 2013-11-24 18:21:30.083 |
| X | CONVERT | 53 | 227647 | 0x00000002F9B12FC0 | 227647 | droptemp | 2013-11-24 18:36:28.267 |
+--------------+----------------+--------------------+------------------+--------------------+----------------+-------------+-------------------------+
Portanto, o
SELECT INTO
transação está segurando um S
bloqueio na linha em tempdb.sys.sysschobjs
referente à variável de tabela #A1E86130
. O droptemp
a transação não pode obter um X
lock nesta linha devido a este S
conflitante trancar. Executar esta consulta repetidamente revela que o
transaction_id
para o droptemp
transação muda repetidamente. Eu especulo que o SQL Server deve alocar essas transações internas em spids de usuário e priorizá-las antes de fazer o trabalho do usuário. Portanto, o ID de sessão 53 está preso em um ciclo constante em que inicia um
droptemp
transação, é bloqueado pela transação do usuário em execução no mesmo spid. Reverte a transação interna e repete o processo indefinidamente. Isso é comprovado pelo rastreamento dos vários eventos de bloqueio e transação no SQL Server Profiler depois que o spid fica travado.
Eu também rastreei os eventos de bloqueio antes disso.
Bloquear Bloqueio de Eventos
A maioria dos bloqueios de teclas compartilhados retirados pelo
SELECT INTO
transação em chaves em sysschobjs
seja liberado imediatamente. A exceção é o primeiro bloqueio em (246708db8c1f)
. Isso faz algum sentido, pois o plano mostra varreduras de loops aninhados de
[sys].[sysschobjs].[clst] [o]
e como os objetos temporários recebem IDs de objeto negativos, eles serão as primeiras linhas encontradas na ordem de verificação. Também encontrei a situação descrita no OP em que a execução de uma junção cruzada de três vias primeiro parece permitir que a de quatro vias seja bem-sucedida.
Os primeiros eventos no rastreamento para o
SELECT INTO
transação há um padrão totalmente diferente. Isso ocorreu após uma reinicialização do serviço, portanto, os valores do recurso de bloqueio na coluna de dados de texto não são diretamente comparáveis.
Em vez de manter o bloqueio na primeira chave e, em seguida, um padrão de aquisição e liberação de chaves subsequentes, parece adquirir muito mais bloqueios sem liberá-los inicialmente.
Presumo que deve haver alguma variação na estratégia de execução que evite o problema.
Atualizar
O item de conexão que criei sobre isso não foi marcado como fixo, mas agora estou no SQL Server 2012 SP2 e agora só posso reproduzir o autobloqueio temporário em vez de permanente. Eu ainda recebo o autobloqueio, mas depois de algumas tentativas fracassadas de executar o
droptemp
transação com sucesso, parece voltar a processar a transação do usuário. Depois disso, confirma a transação do sistema e é executada com sucesso. Ainda no mesmo spid. (oito tentativas em uma execução de exemplo. Não tenho certeza se isso será repetido consistentemente)