Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Por que essa consulta está lenta na primeira vez depois de iniciar o serviço?


Eu posso também poderia reproduzir isso 100% do tempo na minha máquina. (ver nota no final)

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)