Je peux pourrait également reproduire cela 100% du temps sur ma machine. (voir note à la fin)
L'essentiel du problème est que vous supprimez S
verrouille les lignes de la table système dans tempdb
qui peuvent entrer en conflit avec les verrous nécessaires pour tempdb
interne opérations de nettoyage.
Lorsque ce travail de nettoyage est alloué à la même session qui possède le S
verrouiller un blocage indéfini peut se produire.
Pour éviter ce problème à coup sûr, vous devez arrêter de référencer le system
objets à l'intérieur de tempdb
.
Il est possible de créer une table de nombres sans référencer aucune table externe. Ce qui suit n'a besoin de lire aucune ligne de table de base et ne prend donc aucun verrou.
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
Étapes pour reproduire
Créez d'abord une procédure
CREATE PROC P
AS
SET NOCOUNT ON;
DECLARE @T TABLE (X INT)
GO
Redémarrez ensuite le service SQL et exécutez en une seule connexion
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
Ensuite, dans une autre connexion, exécutez
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 requête remplissant la table Numbers semble réussir à entrer dans une situation de verrouillage en direct avec les transactions système internes qui nettoient les objets temporaires tels que les variables de table.
J'ai réussi à bloquer l'identifiant de session 53 de cette manière. Il est bloqué indéfiniment. La sortie de sp_WhoIsActive
montre que ce spid passe presque tout le temps en suspension. Dans des exécutions consécutives, les nombres dans le reads
colonne augmente mais les valeurs des autres colonnes restent largement les mêmes.
La durée d'attente ne montre pas de tendance croissante, mais indique qu'elle doit être débloquée périodiquement avant d'être bloquée à nouveau.
SELECT *
FROM sys.dm_os_waiting_tasks
WHERE session_id = blocking_session_id
Retours
+----------------------+------------+-----------------+------------------+-----------+--------------------+-----------------------+---------------------+--------------------------+--------------------------------------------------------------------------------------------------+
| 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 |
+----------------------+------------+-----------------+------------------+-----------+--------------------+-----------------------+---------------------+--------------------------+--------------------------------------------------------------------------------------------------+
Utilisation de l'identifiant dans la description de la ressource
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
Retours
+------------+
| name |
+------------+
| sysschobjs |
+------------+
Courir
SELECT resource_description,request_status
FROM sys.dm_tran_locks
WHERE request_session_id = 53 AND request_status <> 'GRANT'
Retours
+----------------------+----------------+
| resource_description | request_status |
+----------------------+----------------+
| (246708db8c1f) | CONVERT |
+----------------------+----------------+
Connexion via le DAC et exécution
SELECT id,name
FROM tempdb.sys.sysschobjs WITH (NOLOCK)
WHERE %%LOCKRES%% = '(246708db8c1f)'
Retours
+-------------+-----------+
| id | name |
+-------------+-----------+
| -1578606288 | #A1E86130 |
+-------------+-----------+
Curieux de savoir ce que c'est
SELECT name,user_type_id
FROM tempdb.sys.columns
WHERE object_id = -1578606288
Retours
+------+--------------+
| name | user_type_id |
+------+--------------+
| X | 56 |
+------+--------------+
Il s'agit du nom de la colonne dans la variable de table utilisée par la procédure stockée.
Courir
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)'
Retours
+--------------+----------------+--------------------+------------------+--------------------+----------------+-------------+-------------------------+
| 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 |
+--------------+----------------+--------------------+------------------+--------------------+----------------+-------------+-------------------------+
Ainsi, le SELECT INTO
la transaction contient un S
verrou sur la ligne dans tempdb.sys.sysschobjs
appartenant à la variable de table #A1E86130
. Le droptemp
la transaction ne peut pas obtenir un X
verrouille cette ligne à cause de ce S
conflictuel verrouiller.
L'exécution répétée de cette requête révèle que le transaction_id
pour le droptemp
transaction change à plusieurs reprises.
Je suppose que SQL Server doit allouer ces transactions internes sur les spids utilisateur et les hiérarchiser avant de faire le travail de l'utilisateur. Ainsi, l'identifiant de session 53 est bloqué dans un cycle constant où il démarre un droptemp
transaction, est bloquée par la transaction utilisateur exécutée sur le même spid. Annule la transaction interne puis répète le processus indéfiniment.
Ceci est confirmé par le suivi des divers événements de verrouillage et de transaction dans SQL Server Profiler après le blocage du spid.
J'ai également tracé les événements de verrouillage avant cela.
Verrouiller le blocage des événements
La plupart des serrures à clé partagées prises par le SELECT INTO
transaction sur les clés dans sysschobjs
être libéré immédiatement. L'exception est le premier verrou sur (246708db8c1f)
.
Cela a du sens car le plan montre des analyses de boucles imbriquées de [sys].[sysschobjs].[clst] [o]
et comme les objets temporaires reçoivent des ID d'objet négatifs, ils seront les premières lignes rencontrées dans l'ordre d'analyse.
J'ai également rencontré la situation décrite dans l'OP où l'exécution d'une jointure croisée à trois voies semble d'abord permettre à la jointure à quatre voies de réussir.
Les premiers événements de la trace pour SELECT INTO
transaction, il existe un modèle entièrement différent.
C'était après un redémarrage du service, donc les valeurs de ressource de verrouillage dans la colonne de données de texte ne sont pas directement comparables.
Au lieu de conserver le verrou sur la première clé, puis d'acquérir et de libérer les clés suivantes, il semble acquérir beaucoup plus de verrous sans les libérer au départ.
Je suppose qu'il doit y avoir une certaine variance dans la stratégie d'exécution qui évite le problème.
Mettre à jour
L'élément de connexion que j'ai soulevé à ce sujet
n'a pas été marqué comme corrigé, mais je suis maintenant sur SQL Server 2012 SP2 et je ne peux plus reproduire que l'autoblocage temporaire plutôt que permanent. J'obtiens toujours l'autoblocage, mais après un certain nombre de tentatives infructueuses pour exécuter le droptemp
transaction réussie, il semble revenir au traitement de la transaction utilisateur. Après cela, la transaction système est validée puis exécutée avec succès. Toujours sur le même spid. (huit tentatives dans un exemple d'exécution. Je ne sais pas si cela se répétera systématiquement)