sql >> Base de Datos >  >> RDS >> Sqlserver

¿Por qué esta consulta es lenta la primera vez después de iniciar el servicio?

Yo puedo también podría reproducir esto el 100% del tiempo en mi máquina. (ver nota al final)

La esencia del problema es que estás sacando S bloqueos en las filas de la tabla del sistema en tempdb que pueden entrar en conflicto con los bloqueos necesarios para el tempdb interno transacciones de limpieza.

Cuando este trabajo de limpieza se asigna a la misma sesión que posee el S puede ocurrir un bloqueo indefinido.

Para evitar este problema con certeza, debe dejar de hacer referencia al system objetos dentro de tempdb .

Es posible crear una tabla de números sin hacer referencia a ninguna tabla externa. Lo siguiente no necesita leer filas de la tabla base y, por lo tanto, tampoco requiere bloqueos.

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 

Pasos para reproducir

Primero cree un procedimiento

CREATE PROC P
AS
    SET NOCOUNT ON;

    DECLARE @T TABLE (X INT)
GO

Luego reinicie el Servicio SQL y en una conexión ejecute

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 

Luego, en otra conexión ejecutar

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

La consulta que completa la tabla Numbers parece lograr entrar en una situación de bloqueo en vivo con las transacciones internas del sistema que limpian objetos temporales como las variables de la tabla.

Logré bloquear la identificación de sesión 53 de esta manera. Está bloqueado indefinidamente. La salida de sp_WhoIsActive muestra que este spid pasa casi todo el tiempo suspendido. En ejecuciones consecutivas, los números en reads columna aumenta, pero los valores en las otras columnas siguen siendo básicamente los mismos.

La duración de la espera no muestra un patrón creciente, aunque indica que debe desbloquearse periódicamente antes de volver a bloquearse.

SELECT *
FROM   sys.dm_os_waiting_tasks
WHERE  session_id = blocking_session_id

Devoluciones

+----------------------+------------+-----------------+------------------+-----------+--------------------+-----------------------+---------------------+--------------------------+--------------------------------------------------------------------------------------------------+
| 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 la identificación en la descripción del 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 

Devoluciones

+------------+
|    name    |
+------------+
| sysschobjs |
+------------+

Corriendo

SELECT resource_description,request_status
FROM   sys.dm_tran_locks 
WHERE request_session_id = 53 AND request_status <> 'GRANT'

Devoluciones

+----------------------+----------------+
| resource_description | request_status |
+----------------------+----------------+
| (246708db8c1f)       | CONVERT        |
+----------------------+----------------+

Conexión a través del DAC y ejecución

SELECT id,name
FROM   tempdb.sys.sysschobjs WITH (NOLOCK)
WHERE %%LOCKRES%% = '(246708db8c1f)' 

Devoluciones

+-------------+-----------+
|     id      |   name    |
+-------------+-----------+
| -1578606288 | #A1E86130 |
+-------------+-----------+

Curiosidad por lo que es

SELECT name,user_type_id
FROM tempdb.sys.columns
WHERE object_id = -1578606288 

Devoluciones

+------+--------------+
| name | user_type_id |
+------+--------------+
| X    |           56 |
+------+--------------+

Este es el nombre de la columna en la variable de tabla utilizada por el proceso almacenado.

Corriendo

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)' 

Devoluciones

+--------------+----------------+--------------------+------------------+--------------------+----------------+-------------+-------------------------+
| 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 |
+--------------+----------------+--------------------+------------------+--------------------+----------------+-------------+-------------------------+

Así que SELECT INTO la transacción tiene una S bloqueo en la fila en tempdb.sys.sysschobjs perteneciente a la variable de tabla #A1E86130 . El droptemp la transacción no puede obtener una X bloqueo en esta fila debido a este conflicto S bloquear.

Ejecutar esta consulta repetidamente revela que el transaction_id para el droptemp la transacción cambia repetidamente.

Especulo que SQL Server debe asignar estas transacciones internas en los spids de los usuarios y priorizarlas antes de realizar el trabajo del usuario. Entonces, la ID de sesión 53 está atascada en un ciclo constante en el que inicia un droptemp transacción, está bloqueada por la transacción de usuario que se ejecuta en el mismo spid. Revierte la transacción interna y luego repite el proceso indefinidamente.

Esto se confirma mediante el seguimiento de los diversos eventos de bloqueo y transacción en SQL Server Profiler después de que el spid se bloquee.

También rastreé los eventos de bloqueo antes de eso.

Bloqueo de eventos de bloqueo

La mayoría de los bloqueos de teclas compartidas eliminados por SELECT INTO transacción en claves en sysschobjs ser liberado de inmediato. La excepción es el primer bloqueo en (246708db8c1f) .

Esto tiene sentido ya que el plan muestra escaneos de bucles anidados de [sys].[sysschobjs].[clst] [o] y debido a que a los objetos temporales se les asignan objectid negativos, serán las primeras filas encontradas en el orden de escaneo.

También me encontré con la situación descrita en el OP donde ejecutar una unión cruzada de tres vías primero parece permitir que la de cuatro vías tenga éxito.

Los primeros eventos en el seguimiento de SELECT INTO transacción hay un patrón completamente diferente.

Esto fue después de un reinicio del servicio, por lo que los valores de recursos de bloqueo en la columna de datos de texto no son directamente comparables.

En lugar de retener el candado en la primera llave y luego un patrón de adquisición y liberación de llaves subsiguientes, parece adquirir muchos más candados sin liberarlos inicialmente.

Supongo que debe haber alguna variación en la estrategia de ejecución que evite el problema.

Actualizar

El elemento de conexión que planteé sobre esto no se ha marcado como reparado, pero ahora estoy en SQL Server 2012 SP2 y ahora solo puedo reproducir autobloqueo temporal en lugar de permanente. Todavía obtengo el autobloqueo, pero después de una cantidad de intentos fallidos de ejecutar droptemp transacción con éxito parece volver a procesar la transacción del usuario. Después de eso, la transacción del sistema se confirma y luego se ejecuta con éxito. Todavía en el mismo spid. (ocho intentos en una ejecución de ejemplo. No estoy seguro de si esto se repetirá constantemente)