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)