Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Pourquoi cette requête est-elle lente la première fois après le démarrage du service ?

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)