Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Perché questa query è lenta la prima volta dopo l'avvio del servizio?

Io posso potrebbe anche riprodurre questo 100% delle volte sulla mia macchina. (vedi nota in fondo)

Il succo del problema è che stai eliminando S blocca le righe della tabella di sistema in tempdb che possono entrare in conflitto con i blocchi necessari per tempdb interni transazioni di pulizia.

Quando questo lavoro di pulizia viene assegnato alla stessa sessione che possiede la S lock può verificarsi un blocco indefinito.

Per evitare questo problema, devi smettere di fare riferimento al system oggetti all'interno di tempdb .

È possibile creare una tabella numeri senza fare riferimento a tabelle esterne. Quanto segue non ha bisogno di leggere righe della tabella di base e quindi non accetta blocchi.

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 

Passaggi per la riproduzione

Per prima cosa crea una procedura

CREATE PROC P
AS
    SET NOCOUNT ON;

    DECLARE @T TABLE (X INT)
GO

Quindi riavvia il servizio SQL e in una connessione esegui

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 

Quindi in un'altra connessione esegui

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 query che popola la tabella di Numbers sembra riuscire a entrare in una situazione di blocco in tempo reale con le transazioni interne del sistema che ripuliscono oggetti temporanei come le variabili di tabella.

Sono riuscito a bloccare l'ID sessione 53 in questo modo. È bloccato a tempo indeterminato. L'output di sp_WhoIsActive mostra che questo spid trascorre quasi tutto il tempo sospeso. In esecuzioni consecutive i numeri nelle reads colonna aumenta ma i valori nelle altre colonne rimangono sostanzialmente gli stessi.

La durata dell'attesa non mostra uno schema crescente, sebbene indichi che deve essere sbloccata periodicamente prima di essere nuovamente bloccata.

SELECT *
FROM   sys.dm_os_waiting_tasks
WHERE  session_id = blocking_session_id

Resi

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

Utilizzando l'id nella descrizione della risorsa

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 

Resi

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

In esecuzione

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

Resi

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

Connessione tramite DAC e funzionamento

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

Resi

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

Curioso di sapere di cosa si tratta

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

Resi

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

Questo è il nome della colonna nella variabile di tabella utilizzata dal processo memorizzato

In esecuzione

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

Resi

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

Quindi il SELECT INTO transazione contiene una S blocca la riga in tempdb.sys.sysschobjs relativo alla variabile di tabella #A1E86130 . Il droptemp la transazione non può ottenere una X blocca questa riga a causa di questo S in conflitto serratura.

L'esecuzione ripetuta di questa query rivela che il transaction_id per il droptemp transazione cambia ripetutamente.

Suppongo che SQL Server debba allocare queste transazioni interne sugli spids degli utenti e dare loro la priorità prima di eseguire il lavoro dell'utente. Quindi l'id di sessione 53 è bloccato in un ciclo costante in cui avvia un droptemp transazione, è bloccato dalla transazione utente in esecuzione sullo stesso spid. Esegue il rollback della transazione interna, quindi ripete il processo all'infinito.

Ciò è confermato dalla traccia dei vari eventi di blocco e transazione in SQL Server Profiler dopo che lo spid si è bloccato.

Ho anche tracciato gli eventi di blocco prima di quello.

Blocca il blocco degli eventi

La maggior parte dei blocchi a chiave condivisa eliminati dal SELECT INTO transazione sulle chiavi in ​​sysschobjs essere rilasciato immediatamente. L'eccezione è il primo blocco su (246708db8c1f) .

Questo ha un senso in quanto il piano mostra le scansioni dei loop nidificati di [sys].[sysschobjs].[clst] [o] e poiché agli oggetti temporanei vengono assegnati ID oggetti negativi, saranno le prime righe incontrate nell'ordine di scansione.

Ho anche riscontrato la situazione descritta nell'OP in cui l'esecuzione di un cross join a tre vie sembra consentire il successo di quella a quattro vie.

I primi eventi nella traccia per SELECT INTO transazione ci sono uno schema completamente diverso.

Ciò è avvenuto dopo il riavvio del servizio, quindi i valori delle risorse di blocco nella colonna dei dati di testo non sono direttamente confrontabili.

Invece di mantenere il blocco sulla prima chiave e quindi uno schema di acquisizione e rilascio delle chiavi successive, sembra acquisire molti più blocchi senza rilasciarli inizialmente.

Presumo che debba esserci qualche variazione nella strategia di esecuzione che eviti il ​​problema.

Aggiorna

L'elemento Connect che ho sollevato su questo non è stato contrassegnato come corretto, ma ora sono su SQL Server 2012 SP2 e ora posso riprodurre solo il blocco automatico temporaneo anziché permanente. Ottengo ancora il blocco automatico ma dopo un certo numero di tentativi falliti di eseguire droptemp transazione riuscita sembra tornare all'elaborazione della transazione utente. Dopo aver eseguito il commit, la transazione di sistema viene eseguita correttamente. Sempre allo stesso ritmo. (otto tentativi eseguiti in un esempio. Non sono sicuro che questo verrà ripetuto in modo coerente)