Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Warum ist diese Abfrage beim ersten Mal langsam, nachdem ich den Dienst gestartet habe?

Ich kann konnte dies auch zu 100% auf meiner Maschine reproduzieren. (siehe Hinweis am Ende)

Der Kern des Problems ist, dass Sie S ausschalten Sperrt Systemtabellenzeilen in tempdb das kann mit den Sperren kollidieren, die für interne tempdb benötigt werden Bereinigungstransaktionen.

Wenn diese Bereinigungsarbeit derselben Sitzung zugewiesen wird, die den S besitzt lock kann es zu einem unbestimmten Hängenbleiben kommen.

Um dieses Problem sicher zu vermeiden, müssen Sie aufhören, auf das system zu verweisen Objekte in tempdb .

Es ist möglich, eine Zahlentabelle zu erstellen, ohne überhaupt auf externe Tabellen zu verweisen. Das Folgende muss keine Basistabellenzeilen lesen und nimmt daher auch keine Sperren.

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 

Schritte zum Reproduzieren

Erstellen Sie zuerst eine Prozedur

CREATE PROC P
AS
    SET NOCOUNT ON;

    DECLARE @T TABLE (X INT)
GO

Starten Sie dann den SQL-Dienst neu und führen Sie ihn in einer Verbindung aus

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 

Dann in einer anderen Verbindung laufen

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

Die Abfrage, die die Numbers-Tabelle füllt, scheint es zu schaffen, in eine Live-Lock-Situation mit den internen Systemtransaktionen zu geraten, die temporäre Objekte wie Tabellenvariablen bereinigen.

Ich habe es geschafft, die Sitzungs-ID 53 auf diese Weise zu blockieren. Es ist auf unbestimmte Zeit gesperrt. Die Ausgabe von sp_WhoIsActive zeigt, dass dieser Spid fast die ganze Zeit ausgesetzt ist. In aufeinanderfolgenden Läufen werden die Zahlen im reads Spalte erhöht, aber die Werte in den anderen Spalten bleiben weitgehend gleich.

Die Wartezeit zeigt kein ansteigendes Muster, weist jedoch darauf hin, dass sie regelmäßig entsperrt werden muss, bevor sie wieder gesperrt wird.

SELECT *
FROM   sys.dm_os_waiting_tasks
WHERE  session_id = blocking_session_id

Rückgabe

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

Verwenden der ID in der Ressourcenbeschreibung

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 

Rückgabe

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

Laufen

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

Rückgabe

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

Über den DAC verbinden und ausführen

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

Rückgabe

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

Neugierig, was das ist

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

Rückgabe

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

Dies ist der Spaltenname in der Tabellenvariablen, die von der gespeicherten Prozedur verwendet wird.

Laufen

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

Rückgabe

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

Also das SELECT INTO Transaktion enthält ein S Sperren Sie die Zeile in tempdb.sys.sysschobjs die sich auf die Tabellenvariable #A1E86130 beziehen . Die droptemp Transaktion kann kein X erhalten Sperren Sie diese Zeile wegen dieses widersprüchlichen S sperren.

Das wiederholte Ausführen dieser Abfrage zeigt, dass die transaction_id für droptemp Transaktion ändert sich wiederholt.

Ich spekuliere, dass SQL Server diese internen Transaktionen auf Benutzer-Spids zuweisen und sie priorisieren muss, bevor die Benutzerarbeit ausgeführt wird. Die Sitzungs-ID 53 steckt also in einem konstanten Zyklus, in dem ein droptemp gestartet wird Transaktion, wird von der Benutzertransaktion blockiert, die auf derselben Spid ausgeführt wird. Setzt die interne Transaktion zurück und wiederholt dann den Vorgang auf unbestimmte Zeit.

Dies wird bestätigt, indem die verschiedenen Sperr- und Transaktionsereignisse in SQL Server Profiler nachverfolgt werden, nachdem die Spid hängen geblieben ist.

Ich habe auch die Sperrereignisse davor verfolgt.

Blockieren von Ereignissen sperren

Die meisten gemeinsam genutzten Schlüsselsperren wurden durch SELECT INTO entfernt Transaktion auf Schlüsseln in sysschobjs sofort entlassen werden. Die Ausnahme ist die erste Sperre auf (246708db8c1f) .

Dies ist sinnvoll, da der Plan verschachtelte Schleifen-Scans von [sys].[sysschobjs].[clst] [o] zeigt und da temporäre Objekte negative Objekt-IDs erhalten, sind sie die ersten Zeilen, die in der Scan-Reihenfolge gefunden werden.

Ich bin auch auf die im OP beschriebene Situation gestoßen, in der das Ausführen eines Drei-Wege-Cross-Join zuerst den Vier-Wege-Join zum Erfolg zu führen scheint.

Die ersten paar Ereignisse im Trace für SELECT INTO Transaktion gibt es ein ganz anderes Muster.

Dies geschah nach einem Dienstneustart, sodass die Sperrressourcenwerte in der Textdatenspalte nicht direkt vergleichbar sind.

Anstatt die Sperre des ersten Schlüssels beizubehalten und dann nachfolgende Schlüssel zu erwerben und freizugeben, scheint es viel mehr Sperren zu erwerben, ohne sie zunächst freizugeben.

Ich nehme an, es muss eine Abweichung in der Ausführungsstrategie geben, die das Problem vermeidet.

Aktualisieren

Der von mir ausgelöste Connect-Gegenstand dazu wurde nicht als behoben markiert, aber ich bin jetzt auf SQL Server 2012 SP2 und kann jetzt nur noch temporäre Selbstblockierung statt permanent reproduzieren. Ich bekomme immer noch die Selbstblockierung, aber nach einigen fehlgeschlagenen Versuchen, den droptemp auszuführen Transaktion erfolgreich, scheint es, zur Verarbeitung der Benutzertransaktion zurückzukehren. Danach wird die Systemtransaktion erfolgreich ausgeführt. Immer noch auf dem gleichen Spid. (acht Versuche in einem Beispiellauf. Ich bin mir nicht sicher, ob dies konsequent wiederholt wird)