2010-05-31 2 views
5

Je reçois beaucoup de blocages dans ma grande application web.J'ai des données sur les blocages, mais je ne comprends pas pourquoi ils se produisent

How to automatically re-run deadlocked transaction? (ASP.NET MVC/SQL Server)

Ici, je voulais ré-exécuter des transactions dans l'impasse, mais on m'a dit de se débarrasser des blocages - il est beaucoup mieux, que d'essayer d'attraper les blocages.

J'ai donc passé toute la journée avec SQL Profiler, en réglant les touches de traçage, etc. Et c'est ce que j'ai obtenu. Il existe une table Users. J'ai une page utile très élevée avec la requête suivante (ce n'est pas la seule requête, mais il est celui qui provoque des troubles)

UPDATE Users 
SET views = views + 1 
WHERE ID IN (SELECT AuthorID FROM Articles WHERE ArticleID = @ArticleID) 

Et puis il y a la requête suivante dans TOUTES pages:

User = DB.Users.SingleOrDefault(u => u.Password == password && u.Name == username); 

C'est là que j'obtiens l'utilisateur des cookies. Très souvent un blocage se produit et cette deuxième requête Linq-to-SQL est choisie comme victime, elle n'est donc pas exécutée et les utilisateurs de mon site voient un écran d'erreur.

Cette information sur le graphique .XDL capturé par SQL Profiler (Il est juste la première impasse, ce n'est pas la seule La liste complète est gigantesque..):

<deadlock-list> 
    <deadlock victim="process824df048"> 
     <process-list> 
      <process id="process824df048" taskpriority="0" logused="0" waitresource="PAGE: 7:1:13921" waittime="1830" ownerId="91418" transactionname="SELECT" lasttranstarted="2010-05-31T12:17:37.663" XDES="0x868175e0" lockMode="S" schedulerid="2" kpid="5076" status="suspended" spid="72" sbid="0" ecid="2" priority="0" trancount="0" lastbatchstarted="2010-05-31T12:17:37.663" lastbatchcompleted="2010-05-31T12:17:37.663" clientapp=".Net SqlClient Data Provider" hostname="WIN-S41KV2CLS67" hostpid="6920" isolationlevel="read committed (2)" xactid="91418" currentdb="7" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056"> 
       <executionStack> 
        <frame procname="adhoc" line="1" stmtstart="74" sqlhandle="0x02000000de1cb30b5b2e40e31ffb345af3c7529430b559c2"> 
*password-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------  </frame> 
        <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000"> 
unknown  </frame> 
       </executionStack> 
       <inputbuf> 
       </inputbuf> 
      </process> 
      <process id="process8765fb88" taskpriority="0" logused="216" waitresource="PAGE: 7:1:14196" waittime="1822" ownerId="91408" transactionname="UPDATE" lasttranstarted="2010-05-31T12:17:37.640" XDES="0x86978e90" lockMode="IX" schedulerid="2" kpid="5216" status="suspended" spid="73" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2010-05-31T12:17:37.557" lastbatchcompleted="2010-05-31T12:17:37.557" clientapp=".Net SqlClient Data Provider" hostname="WIN-S41KV2CLS67" hostpid="6920" loginname="sdfkj93jks9sl" isolationlevel="read committed (2)" xactid="91408" currentdb="7" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056"> 
       <executionStack> 
        <frame procname="database.dbo.UpdateUserStats" line="31" stmtstart="1794" stmtend="2088" sqlhandle="0x03000700bac8836333e58f00879d00000100000000000000"> 
UPDATE Users 
    SET Views = Views + 1 
    WHERE ID IN (SELECT AuthorID FROM Articles WHERE ArticleID = @ArticleID)  </frame> 
        <frame procname="adhoc" line="1" stmtstart="84" sqlhandle="0x01000700b7c78e0760dd3f81000000000000000000000000"> 
EXEC @RETURN_VALUE = [dbo].[UpdateUserStats] @UserID = @p0 </frame> 
        <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000"> 
unknown  </frame> 
       </executionStack> 
       <inputbuf> 
(@p0 int,@RETURN_VALUE int output)EXEC @RETURN_VALUE = [dbo].[UpdateUserStats] @UserID = @p0 </inputbuf> 
      </process> 
      <process id="process86ce0988" taskpriority="0" logused="10000" waittime="1806" schedulerid="1" kpid="2604" status="suspended" spid="72" sbid="0" ecid="0" priority="0" trancount="0" lastbatchstarted="2010-05-31T12:17:37.663" lastbatchcompleted="2010-05-31T12:17:37.663" clientapp=".Net SqlClient Data Provider" hostname="WIN-S41KV2CLS67" hostpid="6920" loginname="sdfkj93jks9sl" isolationlevel="read committed (2)" xactid="91418" currentdb="7" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056"> 
       <executionStack> 
        <frame procname="adhoc" line="1" stmtstart="74" sqlhandle="0x02000000de1cb30b5b2e40e31ffb345af3c7529430b559c2"> 
*password-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------  </frame> 
        <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000"> 
unknown  </frame> 
       </executionStack> 
       <inputbuf> 
*password-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- </inputbuf> 
      </process> 
     </process-list> 
     <resource-list> 
      <pagelock fileid="1" pageid="13921" dbid="7" objectname="database.dbo.Users" id="lock85535c80" mode="IX" associatedObjectId="72057594046382080"> 
       <owner-list> 
        <owner id="process8765fb88" mode="IX"/> 
       </owner-list> 
       <waiter-list> 
        <waiter id="process824df048" mode="S" requestType="wait"/> 
       </waiter-list> 
      </pagelock> 
      <pagelock fileid="1" pageid="14196" dbid="7" objectname="database.dbo.Users" id="lock8469f980" mode="SIU" associatedObjectId="72057594046382080"> 
       <owner-list> 
        <owner id="process86ce0988" mode="S"/> 
       </owner-list> 
       <waiter-list> 
        <waiter id="process8765fb88" mode="IX" requestType="convert"/> 
       </waiter-list> 
      </pagelock> 
      <exchangeEvent id="Pipe894b0680" WaitType="e_waitPipeGetRow" nodeId="0"> 
       <owner-list> 
        <owner id="process824df048"/> 
       </owner-list> 
       <waiter-list> 
        <waiter id="process86ce0988"/> 
       </waiter-list> 
      </exchangeEvent> 
     </resource-list> 
    </deadlock> 

Je lis beaucoup de choses sur les interblocages ... Et je ne comprends pas pourquoi cela provoque une impasse.

Il est donc évident que ces deux requêtes s'exécutent très souvent. Au moins une fois par seconde. Peut-être même plus souvent (300-400 utilisateurs en ligne). Donc, ils peuvent être courus en même temps très facilement, mais pourquoi cela entraîne-t-il une impasse? S'il vous plaît aider.

Merci

+1

Êtes-vous sur SQL2005 ou plus tard? Si oui, avez-vous obtenu le graphique Deadlock de SQL Profiler? Connaissez-vous également le niveau d'isolation de la transaction dans lequel vos requêtes s'exécutent? –

+0

Oui, j'ai le graphique. C'est de là que j'ai eu cette information. Je ne sais rien de l'isolation des transactions.Où puis-je le vérifier? – Alex

+1

Dans la trace du profileur, cliquez avec le bouton droit sur l'événement graphe interblocage et choisissez Extraire les données d'événement, enregistrez-le en tant que xml. Ensuite, ouvrez cela dans le bloc-notes et de trouver "isolationlevel" –

Répondre

11

Vous devez capturer le graphe de blocage. Attachez Profiler et capturez la classe Deadlock Graph Event. Enregistrez le graphique .XDL et ajoutez cette information à votre message.

Jusque-là, est assez évident que votre DB.Users.requête SingleOrDefault nécessite un index sur Nom au moins, sinon sur Nom et Mot de passe:

CREATE INDEX idxUsersNamePassword on Users(Name,Password); 

J'attend les utilisateurs dispose déjà d'un index sur ID et articles a un indice sur ArticleID qui couvre AuthorID aussi. En supposant que Users.ID et Articles.ArticleID sont des PK dans leurs tables respectives, ils sont probablement la clé en cluster de ce qui est respectif. Cela vaut la peine de vérifier, cependant.

Et, comme je l'ai déjà répondu vous une fois dans votre post précédent vous avez décidé de passer à autre chose et de laisser sans réponses, vous devriez envisager d'allumer Snapshot Isolation:

ALTER DATABASE ... SET READ_COMMITTED_SNAPSHOT ON 

En outre, le stockage mot de passe en texte clair est un #fail majeur.

Mise à jour après les informations de blocage

Il existe trois processus (demandes):

  • A) ... F048 qui exécute le SELECT ... FROM Users WHERE Password = ... and Name = ...
  • B) ... 0988 qui est exécution du SELECT ... FROM Users WHERE Password = ... and Name = ...
  • C) ... FB88 qui exécute le UPDATE ...

Le cycle de blocage est la suivante:

  1. C attend le verrou de IX en page, est bloqué par S de un verrou
  2. attend B sur la serrure de la page, est bloqué par des C verrou IX
  3. A attend sur parallèle l'échange des ressources est bloqué par B

Le cycle est donc C-> A-> B-> C. Du fait que les deux SELECT impliqués décident de 1) utiliser un plan parallèle et 2) utiliser des verrous de page est évident qu'ils font un balayage de bout en bout de l'ensemble de la table des utilisateurs. donc le problème est, comme je l'ai prédit, un manque d'index sur (Nom, Mot de passe) sur les Utilisateurs qui amène la requête à scanner beaucoup trop de données. L'ajout de l'index transformerait le SELECT en un SEEK direct sur l'index Nc et une recherche sur l'index clusterisé, ce qui réduirait considérablement la fenêtre de chevauchement avec l'UPDATE. À l'heure actuelle, la mise à jour est à peu près garantie de conflit avec tous les SELECT, car chaque SELECT est garanti pour lire chaque ligne. L'ajout de l'index atténuera le problème immédiat.

L'utilisation de Snapshot Isolation masque le problème, car les analyses de bout en bout vont toujours se produire à moins que l'index (Name, Password) ne soit ajouté. Ou seulement (Nom) fonctionnera probablement aussi. Pour l'évolutivité future, la mise à jour de la colonne Views sur chaque page page ne fonctionnera pas. La mise à jour différée, la mise à jour du nombre agrégé par lots, la partition verticale de la table Utilisateurs et la suppression de la colonne Vues sont des alternatives viables.

+1

Désolé, j'ai oublié d'accepter votre réponse. Oui, je vais mettre à jour le système de stockage de mot de passe. Je vais mettre à jour ma question avec .XDL info. – Alex

+0

L'information .XDL est affichée. – Alex

+0

Merci beaucoup pour une réponse si détaillée! Je ne suis jamais tombé sur l'indexation d'autres colonnes différentes des clés primaires. Que dois-je faire pour ajouter l'index (Nom, Mot de passe)? J'ai trouvé la fenêtre index/clés dans le concepteur de la table. Il a une clé PK_Users, et je peux ajouter la clé IX_Users pour le nom d'utilisateur, mais comment puis-je l'associer au mot de passe? Merci encore. – Alex

1

Votre question a beaucoup de parallèles avec celle ici Diagnosing Deadlocks in SQL Server 2005

(LINQ to SQL, Lire transaction seul être bloqué, par une lecture transaction d'écriture)

Si vous êtes sur SQL2005 ou plus tard peut-être la mise en place de l'isolement de cliché comme discuté sur ce thread fera le travail. Sinon, veuillez mettre à jour votre message avec les détails de la version que vous utilisez.

+0

Merci, je vais jeter un oeil. – Alex

1

Dans cette situation (c'est-à-dire le type de données que vous lisez et la nature des mises à jour se produisant sur ces données), j'exécuterais la requête de recherche d'utilisateur lors de l'isolation non validée.

Alternativement, un changement plus impliqué. De la description que vous avez posté, je considérerais ne pas maintenir le nombre de vues sur l'enregistrement d'utilisateur. Je voudrais plutôt enregistrer ViewCount contre l'article, puis dervive les vues totales pour un utilisateur de la somme de Articles.ViewCount par AuthorID.