De bonnes raisons d'utiliser SELECT… AVEC XLOCK?

11

Je fais face à des blocages récurrents, dont l'un est un verrou et contient une requête SELECT avec un indice XLOCK qui devient la victime du blocage. L'autre instruction est un INSERT dans l'une des tables qui fait partie de la vue de la première requête.

Vue:

create view dbo.viewE
 as
    select * from dbo.E  
    where myValue > 13000 

Sélectionnez la requête:

select * from dbo.viewE with (XLOCK) where A > GETUTCDATE() 

INSERT Statement:

INSERT INTO [dbo].[E] (myValue,A) VALUES (10,GetDate())

La table sous-jacente dbo.E contient environ 3 millions de lignes dans environ 20 colonnes, dont certaines sont ntext.

En retirant les requêtes et en les simulant manuellement avec deux transactions, le comportement est reproductible. Le comportement change si XLOCK est supprimé de la sélection.

Graphique de blocage:

<deadlock-list>
 <deadlock victim="process222222221">
  <process-list>
   <process id="process222222221" taskpriority="0" logused="0" waitresource="KEY: 5:72057604035644444 (ccdf51accc0c)" waittime="2522" ownerId="27202256401" transactionname="SELECT" lasttranstarted="2015-09-14T16:32:36.160" XDES="0x2f1ec5ca0" lockMode="RangeX-X" schedulerid="15" kpid="12936" status="suspended" spid="359" sbid="0" ecid="0" priority="0" trancount="0" lastbatchstarted="2015-09-14T16:32:36.160" lastbatchcompleted="2015-09-14T16:32:36.160" clientapp="x" hostname="x" hostpid="14536" loginname="x" isolationlevel="serializable (4)" xactid="27202256401" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
     <frame procname="adhoc" line="1" stmtstart="48" sqlhandle="0x02000000611e4523142b2318c47c87313a9b2ba587ff3130">
        SELECT * FROM viewE WITH (XLOCK) WHERE A &lt; GetUtcDate()      </frame>
     <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown     </frame>
    </executionStack>
    <inputbuf>
(@UICulture nvarchar(5))SELECT * FROM viewE WITH (XLOCK) WHERE A &lt; GetUtcDate()    </inputbuf>
   </process>
   <process id="process6022222" taskpriority="0" logused="161152" waitresource="KEY: 5:72057604035644444 (cd874c2ba438)" waittime="1370" ownerId="27202248438" transactionguid="0x8de5ccd6eeef67469c6234af59e44ca5" transactionname="DTCXact" lasttranstarted="2015-09-14T16:32:34.767" XDES="0x4aa0bf950" lockMode="RangeI-N" schedulerid="14" kpid="6636" status="suspended" spid="329" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2015-09-14T16:32:37.300" lastbatchcompleted="2015-09-14T16:32:37.300" clientapp="x" hostname="x" hostpid="14536" loginname="x" isolationlevel="read uncommitted (1)" xactid="27202248438" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
     <frame procname="adhoc" line="1" stmtstart="936" sqlhandle="0x020000004853462f09790a4ddedc0d574c2afa539aef1c0e">
     INSERT INTO [E] ([a], [b], [c],...) VALUES (@aDate, @bDate, @c, ...)
     </frame>
     <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown     </frame>
    </executionStack>
    <inputbuf>INSERT INTO [E] ([a], [b], [c],...) VALUES (@aDate, @bDate, @c, ...)
    </inputbuf>
   </process>
  </process-list>
  <resource-list>
   <keylock hobtid="72057604035644444" dbid="5" objectname="db.dbo.E" indexname="IX_index1" id="lock258b6dc80" mode="X" associatedObjectId="72057604035644444">
    <owner-list>
     <owner id="process6022222" mode="X"/>
    </owner-list>
    <waiter-list>
     <waiter id="process222222221" mode="RangeX-X" requestType="wait"/>
    </waiter-list>
   </keylock>
   <keylock hobtid="72057604035644444" dbid="5" objectname="db.dbo.E" indexname="IX_index1" id="lock7b145c400" mode="RangeX-X" associatedObjectId="72057604035644444">
    <owner-list>
     <owner id="process222222221" mode="RangeX-X"/>
    </owner-list>
    <waiter-list>
     <waiter id="process6022222" mode="RangeI-N" requestType="wait"/>
    </waiter-list>
   </keylock>
  </resource-list>
 </deadlock>
</deadlock-list>

Pour autant que je comprends cela, je regarde un blocage KEYLOCK essentiellement provoqué par une requête d'index non couverte qui utilise un index non cluster et un cluster pour collecter les valeurs requises, non?

Mes questions:

  1. Je ne peux pas créer d'index de couverture en raison des colonnes NTEXT requises impliquées. Est-ce que la réduction drastique du nombre de lignes aidera ici?
  2. Y a-t-il une bonne raison pour laquelle je ne sais tout simplement pas que SELECT est exécuté avec XLOCK? Le blocage se produirait-il également sans XLOCK?
Magier
la source

Réponses:

15

Pour autant que je comprends cela, je regarde un blocage KEYLOCK essentiellement provoqué par une requête d'index non couverte qui utilise un index non cluster et un cluster afin de collecter les valeurs requises, non?

Essentiellement, oui. L'opération de lecture (sélection) accède d'abord à l'index non cluster, puis à l'index cluster (recherche). L'opération d'écriture (insertion) accède d'abord à l'index cluster, puis à l'index non cluster. L'accès aux mêmes ressources dans un ordre différent avec des verrous incompatibles peut entraîner un blocage.

Est-ce que la réduction drastique du nombre de lignes aidera ici?

Cela peut arriver , car moins de ressources sont verrouillées et l'opération aura tendance à se terminer plus rapidement. Si cela aide, cela peut réduire les blocages, mais probablement pas les éliminer (mais lisez la suite).

Y a-t-il une bonne raison pour laquelle je ne sais tout simplement pas que SELECT est exécuté avec XLOCK?

Pas vraiment. Des conseils de verrouillage comme celui-ci sont souvent introduits par des personnes sans une parfaite compréhension du fonctionnement de l'isolement, du verrouillage et des blocages, dans une tentative désespérée de réduire ou d'éliminer un problème.

Le blocage se produirait-il également sans XLOCK?

Non , si la sélection s'exécute réellement à l' isolement de lecture non validée, car les verrous incompatibles ne seront pas pris (et maintenus) dans un ordre différent.

Oui , si un niveau d'isolement de verrouillage est utilisé et que des verrous incompatibles sont pris et maintenus dans un ordre incohérent, par exemple partagé (S) sur le non cluster, puis S sur le cluster lors de la lecture. La probabilité d'un blocage dans ce scénario dépend du nombre de verrous pris et de leur durée.

Conseil

Ce qui ressort vraiment (lors de l'examen), c'est que la transaction de sélection s'exécute sous une isolation sérialisable . Cela pourrait être défini par votre infrastructure ou dû à l'utilisation du DTC (Distributed Transaction Coordinator) - voir transactionname = "DTCXact" dans le graphique de blocage. Vous devriez examiner les raisons de cela et chercher à le changer si possible.

Sans cette escalade vers sérialisable, les chances sont très bonnes que ce blocage ne se produise pas, en supposant que l' XLOCKindice est supprimé. Cela dit, vous lirez sous lire l' isolement non engagé , qui comporte très peu de garanties de cohérence.

Si votre application et le code SQL Server peuvent tolérer la lecture de versions de lignes, passer à la lecture d'isolement de cliché validé (RCSI) ou d' isolement de cliché (SI) pour les lectures éviterait également le blocage ( XLOCKsupprimé!), Tout en présentant un point d' entrée cohérent une vue en temps réel des données validées. Cela suppose également que vous pouvez éviter l'isolement sérialisable, bien sûr.

En fin de compte, l' XLOCKindice est contre-productif, mais vous devez vraiment rechercher la raison de l'utilisation du niveau d'isolement sérialisable. Le trancount = 2est également intéressant - peut-être que vous imbriquez involontairement des transactions ici. Autre chose à vérifier.

Paul White 9
la source
2
  1. Une réduction drastique du nombre de lignes réduira la probabilité d'obtenir l'impasse, mais elle ne disparaîtra pas complètement.

En termes simples, la sélection utilise d'abord l'index pour déterminer les lignes à sélectionner, puis récupère les lignes, tandis que l'insertion insère une ligne, puis essaie de mettre à jour l'index (XLOCKED).

  1. Les développeurs d'applications ont tendance à utiliser XLOCK si, dans la même transaction, ils souhaitent effectuer ultérieurement une mise à jour des données. Cela garantit que personne ne peut mettre à jour les données sous eux. J'examinerais ce que l'application fait pour voir si le XLOCK est requis.

Cela dit, la suppression du XLOCK ne résoudra probablement pas le problème. SELECT sélectionnera toujours un verrou partagé sur l'index, et INSERT voudra qu'un XLOCK le mette à jour. Un verrou partagé et un XLOCK ne peuvent pas exister ensemble sur l'objet, vous obtiendrez donc toujours un blocage. IX_Index1 doit être MyValue ou A, ou les deux.

Ce type de blocage se produit souvent en raison d'index mal conçus et / ou d'un trop grand nombre d'index. Ou du code mal écrit. Votre meilleure option est de voir s'il existe un moyen de réécrire la sélection pour utiliser un autre index.

Leo Miller
la source