Blocage SQL sur la même clé en cluster exclusivement verrouillée (avec NHibernate) lors de la suppression / insertion


29

Je travaille sur ce problème de blocage depuis plusieurs jours maintenant et peu importe ce que je fais, il persiste d'une manière ou d'une autre.

Tout d'abord, la prémisse générale: nous avons des visites avec VisitItems dans une relation un à plusieurs.

VisitItems informations pertinentes:

CREATE TABLE [BAR].[VisitItems] (
    [Id]                INT             IDENTITY (1, 1) NOT NULL,
    [VisitType]         INT             NOT NULL,
    [FeeRateType]       INT             NOT NULL,
    [Amount]            DECIMAL (18, 2) NOT NULL,
    [GST]               DECIMAL (18, 2) NOT NULL,
    [Quantity]          INT             NOT NULL,
    [Total]             DECIMAL (18, 2) NOT NULL,
    [ServiceFeeType]    INT   NOT NULL,
    [ServiceText]       NVARCHAR (200)  NULL,
    [InvoicingProviderId] INT   NULL,
    [FeeItemId]        INT             NOT NULL,
    [VisitId]          INT             NULL,
    [IsDefault] BIT NOT NULL DEFAULT 0, 
    [SourceVisitItemId] INT NULL, 
    [OverrideCode] INT NOT NULL DEFAULT 0, 
    [InvoiceToCentre] BIT NOT NULL DEFAULT 0, 
    [IsSurchargeItem] BIT NOT NULL DEFAULT 0, 
    CONSTRAINT [PK_BAR.VisitItems] PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_BAR.VisitItems_BAR.FeeItems_FeeItem_Id] FOREIGN KEY ([FeeItemId]) REFERENCES [BAR].[FeeItems] ([Id]),
    CONSTRAINT [FK_BAR.VisitItems_BAR.Visits_Visit_Id] FOREIGN KEY ([VisitId]) REFERENCES [BAR].[Visits] ([Id]), 
    CONSTRAINT [FK_BAR.VisitItems_BAR.VisitTypes] FOREIGN KEY ([VisitType]) REFERENCES [BAR].[VisitTypes]([Id]), 
    CONSTRAINT [FK_BAR.VisitItems_BAR.FeeRateTypes] FOREIGN KEY ([FeeRateType]) REFERENCES [BAR].[FeeRateTypes]([Id]),
    CONSTRAINT [FK_BAR.VisitItems_CMN.Users_Id] FOREIGN KEY (InvoicingProviderId) REFERENCES [CMN].[Users] ([Id]),
    CONSTRAINT [FK_BAR.VisitItems_BAR.VisitItems_SourceVisitItem_Id] FOREIGN KEY ([SourceVisitItemId]) REFERENCES [BAR].[VisitItems]([Id]),
    CONSTRAINT [CK_SourceVisitItemId_Not_Equal_Id] CHECK ([SourceVisitItemId] <> [Id]),
    CONSTRAINT [FK_BAR.VisitItems_BAR.OverrideCodes] FOREIGN KEY ([OverrideCode]) REFERENCES [BAR].[OverrideCodes]([Id]),
    CONSTRAINT [FK_BAR.VisitItems_BAR.ServiceFeeTypes] FOREIGN KEY ([ServiceFeeType]) REFERENCES [BAR].[ServiceFeeTypes]([Id])
)

CREATE NONCLUSTERED INDEX [IX_FeeItem_Id]
    ON [BAR].[VisitItems]([FeeItemId] ASC)

CREATE NONCLUSTERED INDEX [IX_Visit_Id]
    ON [BAR].[VisitItems]([VisitId] ASC)

Infos visite:

CREATE TABLE [BAR].[Visits] (
    [Id]                     INT            IDENTITY (1, 1) NOT NULL,
    [VisitType]              INT            NOT NULL,
    [DateOfService]          DATETIMEOFFSET  NOT NULL,
    [InvoiceAnnotation]      NVARCHAR(255)  NULL ,
    [PatientId]              INT            NOT NULL,
    [UserId]                 INT            NULL,
    [WorkAreaId]             INT            NOT NULL, 
    [DefaultItemOverride] BIT NOT NULL DEFAULT 0, 
    [DidNotWaitAdjustmentId] INT NULL, 
    [AppointmentId] INT NULL, 
    CONSTRAINT [PK_BAR.Visits] PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_BAR.Visits_CMN.Patients] FOREIGN KEY ([PatientId]) REFERENCES [CMN].[Patients] ([Id]) ON DELETE CASCADE,
    CONSTRAINT [FK_BAR.Visits_CMN.Users] FOREIGN KEY ([UserId]) REFERENCES [CMN].[Users] ([Id]),
    CONSTRAINT [FK_BAR.Visits_CMN.WorkAreas_WorkAreaId] FOREIGN KEY ([WorkAreaId]) REFERENCES [CMN].[WorkAreas] ([Id]), 
    CONSTRAINT [FK_BAR.Visits_BAR.VisitTypes] FOREIGN KEY ([VisitType]) REFERENCES [BAR].[VisitTypes]([Id]),
    CONSTRAINT [FK_BAR.Visits_BAR.Adjustments] FOREIGN KEY ([DidNotWaitAdjustmentId]) REFERENCES [BAR].[Adjustments]([Id]), 
);

CREATE NONCLUSTERED INDEX [IX_Visits_PatientId]
    ON [BAR].[Visits]([PatientId] ASC);

CREATE NONCLUSTERED INDEX [IX_Visits_UserId]
    ON [BAR].[Visits]([UserId] ASC);

CREATE NONCLUSTERED INDEX [IX_Visits_WorkAreaId]
    ON [BAR].[Visits]([WorkAreaId]);

Plusieurs utilisateurs souhaitent mettre à jour la table VisitItems simultanément de la manière suivante:

Une demande Web distincte créera une visite avec VisitItems (généralement 1). Ensuite (la demande de problème):

  1. La demande Web arrive, ouvre la session NHibernate, démarre la transaction NHibernate (en utilisant la lecture répétable avec READ_COMMITTED_SNAPSHOT activé).
  2. Lisez tous les éléments de visite pour une visite donnée par VisitId .
  3. Le code évalue si les éléments sont toujours pertinents ou si nous en avons besoin de nouveaux en utilisant des règles complexes (donc un peu long terme, par exemple 40 ms).
  4. Le code trouve qu'un élément doit être ajouté, l'ajoute à l'aide de NHibernate Visit.VisitItems.Add (..)
  5. Le code identifie qu'un élément doit être supprimé (pas celui que nous venons d'ajouter), le supprime à l'aide de NHibernate Visit.VisitItems.Remove (élément).
  6. Le code valide la transaction

Avec un outil, je simule 12 requêtes simultanées, ce qui est très susceptible de se produire dans un futur environnement de production.

[MODIFIER] Sur demande, j'ai supprimé un grand nombre des détails de l'enquête que j'avais ajoutés ici pour être bref.

Après de nombreuses recherches, l'étape suivante consistait à trouver un moyen de verrouiller l'indication sur un index différent de celui utilisé dans la clause where (c'est-à-dire la clé primaire, car elle est utilisée pour la suppression), j'ai donc modifié ma déclaration de verrouillage en :

var items = (List<VisitItem>)_session.CreateSQLQuery(@"SELECT * FROM BAR.VisitItems WITH (XLOCK, INDEX([PK_BAR.VisitItems]))
        WHERE VisitId = :visitId")
        .AddEntity(typeof(VisitItem))
        .SetParameter("visitId", qi.Visit.Id)
        .List<VisitItem>();

Cela réduisait légèrement les blocages en fréquence, mais ils se produisaient toujours. Et c'est là que je commence à me perdre:

Trois serrures exclusives?

<deadlock-list>
  <deadlock victim="process3f71e64e8">
    <process-list>
      <process id="process3f71e64e8" taskpriority="0" logused="0" waitresource="KEY: 5:72057594071744512 (a5e1814e40ba)" waittime="3812" ownerId="8004520" transactionname="user_transaction" lasttranstarted="2015-12-14T10:24:58.010" XDES="0x3f7cb43b0" lockMode="X" schedulerid="1" kpid="15788" status="suspended" spid="63" sbid="0" ecid="0" priority="0" trancount="1" lastbatchstarted="2015-12-14T10:24:58.013" lastbatchcompleted="2015-12-14T10:24:58.013" lastattention="1900-01-01T00:00:00.013" clientapp=".Net SqlClient Data Provider" hostname="ABC" hostpid="10016" loginname="bsapp" isolationlevel="repeatable read (3)" xactid="8004520" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
        <executionStack>
          <frame procname="adhoc" line="1" stmtstart="18" stmtend="254" sqlhandle="0x0200000024a9e43033ef90bb631938f939038627209baafb0000000000000000000000000000000000000000">
            unknown
          </frame>
          <frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
            unknown
          </frame>
        </executionStack>
        <inputbuf>
          (@p0 int)SELECT * FROM BAR.VisitItems WITH (XLOCK, INDEX([PK_BAR.VisitItems]))
          WHERE VisitId = @p0
        </inputbuf>
      </process>
      <process id="process4105af468" taskpriority="0" logused="1824" waitresource="KEY: 5:72057594071744512 (8194443284a0)" waittime="3792" ownerId="8004519" transactionname="user_transaction" lasttranstarted="2015-12-14T10:24:58.010" XDES="0x3f02ea3b0" lockMode="S" schedulerid="8" kpid="15116" status="suspended" spid="65" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2015-12-14T10:24:58.033" lastbatchcompleted="2015-12-14T10:24:58.033" lastattention="1900-01-01T00:00:00.033" clientapp=".Net SqlClient Data Provider" hostname="ABC" hostpid="10016" loginname="bsapp" isolationlevel="repeatable read (3)" xactid="8004519" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
        <executionStack>
          <frame procname="adhoc" line="1" stmtstart="18" stmtend="98" sqlhandle="0x0200000075abb0074bade5aa57b8357410941428df4d54130000000000000000000000000000000000000000">
            unknown
          </frame>
          <frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
            unknown
          </frame>
        </executionStack>
        <inputbuf>
          (@p0 int)DELETE FROM BAR.VisitItems WHERE Id = @p0
        </inputbuf>
      </process>
    </process-list>
    <resource-list>
      <keylock hobtid="72057594071744512" dbid="5" objectname="BAR.VisitItems" indexname="PK_BAR.VisitItems" id="lock449e27500" mode="X" associatedObjectId="72057594071744512">
        <owner-list>
          <owner id="process4105af468" mode="X"/>
        </owner-list>
        <waiter-list>
          <waiter id="process3f71e64e8" mode="X" requestType="wait"/>
        </waiter-list>
      </keylock>
      <keylock hobtid="72057594071744512" dbid="5" objectname="BAR.VisitItems" indexname="PK_BAR.VisitItems" id="lock46a525080" mode="X" associatedObjectId="72057594071744512">
        <owner-list>
          <owner id="process3f71e64e8" mode="X"/>
        </owner-list>
        <waiter-list>
          <waiter id="process4105af468" mode="S" requestType="wait"/>
        </waiter-list>
      </keylock>
    </resource-list>
  </deadlock>
</deadlock-list>

Une trace du nombre de requêtes résultant ressemble à ceci.
[EDIT] Whoa. Quelle semaine. J'ai maintenant mis à jour la trace avec la trace non expurgée de la déclaration pertinente qui, je pense, a conduit à l'impasse.

exec sp_executesql N'SELECT * FROM BAR.VisitItems WITH (XLOCK, INDEX([PK_BAR.VisitItems]))
                WHERE VisitId = @p0',N'@p0 int',@p0=3826
go
exec sp_executesql N'SELECT visititems0_.VisitId as VisitId1_, visititems0_.Id as Id1_, visititems0_.Id as Id37_0_, visititems0_.VisitType as VisitType37_0_, visititems0_.FeeItemId as FeeItemId37_0_, visititems0_.FeeRateType as FeeRateT4_37_0_, visititems0_.Amount as Amount37_0_, visititems0_.GST as GST37_0_, visititems0_.Quantity as Quantity37_0_, visititems0_.Total as Total37_0_, visititems0_.ServiceFeeType as ServiceF9_37_0_, visititems0_.ServiceText as Service10_37_0_, visititems0_.InvoiceToCentre as Invoice11_37_0_, visititems0_.IsDefault as IsDefault37_0_, visititems0_.OverrideCode as Overrid13_37_0_, visititems0_.IsSurchargeItem as IsSurch14_37_0_, visititems0_.VisitId as VisitId37_0_, visititems0_.InvoicingProviderId as Invoici16_37_0_, visititems0_.SourceVisitItemId as SourceV17_37_0_ FROM BAR.VisitItems visititems0_ WHERE visititems0_.VisitId=@p0',N'@p0 int',@p0=3826
go
exec sp_executesql N'INSERT INTO BAR.VisitItems (VisitType, FeeItemId, FeeRateType, Amount, GST, Quantity, Total, ServiceFeeType, ServiceText, InvoiceToCentre, IsDefault, OverrideCode, IsSurchargeItem, VisitId, InvoicingProviderId, SourceVisitItemId) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11, @p12, @p13, @p14, @p15); select SCOPE_IDENTITY()',N'@p0 int,@p1 int,@p2 int,@p3 decimal(28,5),@p4 decimal(28,5),@p5 int,@p6 decimal(28,5),@p7 int,@p8 nvarchar(4000),@p9 bit,@p10 bit,@p11 int,@p12 bit,@p13 int,@p14 int,@p15 int',@p0=1,@p1=452,@p2=1,@p3=0,@p4=0,@p5=1,@p6=0,@p7=1,@p8=NULL,@p9=0,@p10=1,@p11=0,@p12=0,@p13=3826,@p14=3535,@p15=NULL
go
exec sp_executesql N'UPDATE BAR.Visits SET VisitType = @p0, DateOfService = @p1, InvoiceAnnotation = @p2, DefaultItemOverride = @p3, AppointmentId = @p4, ReferralRequired = @p5, ReferralCarePlan = @p6, UserId = @p7, PatientId = @p8, WorkAreaId = @p9, DidNotWaitAdjustmentId = @p10, ReferralId = @p11 WHERE Id = @p12',N'@p0 int,@p1 datetimeoffset(7),@p2 nvarchar(4000),@p3 bit,@p4 int,@p5 bit,@p6 nvarchar(4000),@p7 int,@p8 int,@p9 int,@p10 int,@p11 int,@p12 int',@p0=1,@p1='2016-01-22 12:37:06.8915296 +08:00',@p2=NULL,@p3=0,@p4=NULL,@p5=0,@p6=NULL,@p7=3535,@p8=4246,@p9=2741,@p10=NULL,@p11=NULL,@p12=3826
go
exec sp_executesql N'DELETE FROM BAR.VisitItems WHERE Id = @p0',N'@p0 int',@p0=7919
go

Maintenant, mon verrou semble avoir un effet car il apparaît dans le graphique de blocage. Mais quoi? Trois verrous exclusifs et un verrou partagé? Comment cela fonctionne-t-il sur le même objet / clé? Je pensais que tant que vous avez un verrou exclusif, vous ne pouvez pas obtenir un verrou partagé de quelqu'un d'autre? Et l'inverse. Si vous avez un verrou partagé, personne ne peut obtenir un verrou exclusif, ils doivent attendre.

Je pense que je manque ici de compréhension plus approfondie sur le fonctionnement des verrous lorsqu'ils sont pris sur plusieurs clés sur la même table.

Voici quelques-unes des choses que j'ai essayées et leur impact:

  • Ajout d'une autre indication d'index sur IX_Visit_Id à l'instruction de verrouillage. Pas de changement
  • Ajout d'une deuxième colonne à IX_Visit_Id (l'ID de la colonne VisitItem); tiré par les cheveux, mais essayé quand même. Pas de changement
  • Changement du niveau d'isolement en lecture validée (par défaut dans notre projet), des blocages se produisent toujours
  • Changement du niveau d'isolement en sérialisable. Des blocages se produisent toujours, mais pire (graphiques différents). De toute façon, je ne veux pas vraiment faire ça.
  • Prendre un verrou de table les fait disparaître (évidemment), mais qui voudrait faire ça?
  • Prendre un verrou d'application pessimiste (en utilisant sp_getapplock) fonctionne, mais c'est à peu près la même chose que le verrou de table, je ne veux pas faire ça.
  • L'ajout de l'indice READPAST à l'indice XLOCK n'a fait aucune différence
  • J'ai désactivé PageLock sur l'index et PK, aucune différence
  • J'ai ajouté l'indice ROWLOCK à l'indice XLOCK, cela n'a fait aucune différence

Une note secondaire sur NHibernate: La façon dont il est utilisé et je comprends que cela fonctionne est qu'il met en cache les instructions sql jusqu'à ce qu'il trouve vraiment nécessaire de les exécuter, sauf si vous appelez flush, ce que nous essayons de ne pas faire. Ainsi, la plupart des instructions (par exemple la liste agrégée paresseusement chargée de VisitItems => Visit.VisitItems) sont exécutées uniquement lorsque cela est nécessaire. La plupart des instructions de mise à jour et de suppression réelles de ma transaction sont exécutées à la fin lorsque la transaction est validée (comme le montre la trace SQL ci-dessus). Je n'ai vraiment aucun contrôle sur l'ordre d'exécution; NHibernate décide quand faire quoi. Ma déclaration de verrouillage initiale n'est vraiment qu'une solution de contournement.

De plus, avec l'instruction lock, je ne fais que lire les éléments dans une liste inutilisée (je n'essaye pas de remplacer la liste VisitItems sur l'objet Visit car ce n'est pas comme cela que NHibernate est censé fonctionner pour autant que je sache). Donc, même si j'ai lu la liste en premier avec l'instruction personnalisée, NHibernate chargera toujours la liste dans sa collection d'objets proxy Visit.VisitItems en utilisant un appel sql distinct que je peux voir dans la trace quand il est temps de la charger paresseusement quelque part.

Mais cela ne devrait pas avoir d'importance, non? J'ai déjà le verrou sur ladite clé? Le recharger ne changera pas cela?

Pour finir, peut-être pour clarifier: chaque processus ajoute d'abord sa propre visite avec VisitItems, puis entre et la modifie (ce qui déclenchera la suppression et l'insertion et le blocage). Dans mes tests, il n'y a jamais de processus modifiant exactement la même visite ou VisitItems.

Quelqu'un a-t-il une idée sur la façon d'aborder cela plus loin? Tout ce que je peux essayer de contourner cela de manière intelligente (pas de verrous de table, etc.)? J'aimerais aussi savoir pourquoi ce verrou tripple-x est même possible sur le même objet. Je ne comprends pas.

Veuillez me faire savoir si des informations supplémentaires sont nécessaires pour résoudre le puzzle.

[EDIT] J'ai mis à jour la question avec le DDL pour les deux tables concernées.

On m'a également demandé des éclaircissements sur l'attente: oui, quelques blocages ici et là sont ok, nous allons simplement réessayer ou demander à l'utilisateur de soumettre à nouveau (en général). Mais à la fréquence actuelle avec 12 utilisateurs simultanés, je m'attends à ce qu'il n'y en ait qu'un au maximum toutes les quelques heures. Actuellement, ils apparaissent plusieurs fois par minute.

En plus de cela, j'ai obtenu plus d'informations sur le trancount = 2, ce qui pourrait indiquer un problème avec les transactions imbriquées, que nous n'utilisons pas vraiment. Je vais également enquêter sur cela et documenter les résultats ici.


2
N'utilisez pas SELECT *. Cela pourrait être un facteur contributif à vos problèmes. Voir stackoverflow.com/questions/3639861/…
JamieSee

En outre, exécutez SELECT OBJECT_NAME(objectid, dbid) AS objectname, * FROM sys.dm_exec_sql_text(0x0200000024a9e43033ef90bb631938f939038627209baafb0000000000000000000000000000000000000000)le sqlhandle sur chaque trame executionStack pour déterminer davantage ce qui est réellement exécuté.
JamieSee

Êtes-vous confronté à une collision de hachage, peut-être? dba.stackexchange.com/questions/80088/insert-only-deadlocks/…
Johnboy

Hé les gars, j'ai bien peur de ne plus faire partie de ce projet: - /, donc je ne peux pas essayer vos suggestions. J'ai cependant transmis le fil et toutes les informations à certains membres de l'équipe afin qu'ils puissent y jeter un œil à ma place.
Ben

Vous pouvez utiliser ma réponse de script PowerShell à cette question pour obtenir plus de détails de blocage qui peuvent vous aider. Plus précisément, il récupérera les informations de l'instruction SQL pour vos cadres de pile «inconnus». dba.stackexchange.com/questions/28996/…
JamieSee

Réponses:


2

J'ai fait quelques commentaires à cet effet, mais je ne suis pas sûr que vous obteniez les résultats souhaités lorsque vous combinez le niveau d'isolement des transactions en lecture répétable avec un instantané validé en lecture.

Le TIL signalé dans votre liste d'interblocages est une lecture répétable, qui est encore plus restrictive que Read Committed, et étant donné le flux que vous décrivez, conduit probablement à des blocages.

Ce que vous essayez peut-être de faire, c'est que votre DB TIL reste reproductible en lecture, mais définissez la transaction pour utiliser le snapshot TIL explicitement avec une instruction set transaction isolation level. Référence: https://msdn.microsoft.com/en-us/library/ms173763.aspx Si oui, je pense que vous devez avoir quelque chose de incorrect. Je ne connais pas nHibernate, mais il semble qu'il y ait une référence ici: http://www.anujvarma.com/fluent-nhibernate-setting-database-transaction-isolation-level/

Si l'architecture de votre application le permet, une option serait d'essayer de lire l'instantané validé au niveau de la base de données, et si vous obtenez toujours des blocages, activez l'instantané avec le versionnage des lignes. REMARQUE: si vous faites cela, vous devez repenser votre configuration tempdb si vous activez l'instantané (versioning de ligne). Je peux vous fournir toutes sortes de documents à ce sujet si vous en avez besoin - faites le moi savoir.


2

J'ai quelques réflexions. Tout d'abord, le moyen le plus simple d'éviter les interblocages est de toujours prendre les verrous dans le même ordre. Cela signifie qu'un code différent utilisant des transactions explicites doit accéder aux objets dans le même ordre, mais également accéder aux lignes individuellement par clé dans une transaction explicite doit être trié sur cette clé. Essayez de trier Visit.VisitItemspar son PK avant de le faire Addou à Deletemoins qu'il s'agisse d'une énorme collection, auquel cas je trierais SELECT.

Le tri n'est probablement pas votre problème ici. Je suppose que 2 threads saisissent des verrous partagés sur tous les VisitItemIDs pour une donnée VisitIDet que le thread A ne DELETEpeut pas terminer jusqu'à ce que le thread B libère son verrou partagé, ce qu'il ne fera pas jusqu'à ce qu'il soit DELETEterminé. Les verrous d'application fonctionneront ici et ne sont pas aussi mauvais que les verrous de table car ils ne bloquent que par méthode et d'autres SELECTs fonctionneront très bien. Vous pouvez également prendre un verrou exclusif sur la Visittable pour le donné, VisitIDmais encore une fois, c'est potentiellement exagéré.

Je recommanderais de transformer votre suppression matérielle en une suppression UPDATE ... SET IsDeleted = 1logicielle ( au lieu d'utiliser DELETE) et de nettoyer ces enregistrements plus tard, en bloc, à l'aide d'une tâche de nettoyage qui n'utilise pas de transactions explicites. Cela nécessitera évidemment une refactorisation d'un autre code pour ignorer ces lignes supprimées, mais c'est ma méthode préférée pour gérer les DELETEs inclus dans un SELECTdans une transaction explicite.

Vous pouvez également supprimer le SELECTde la transaction et passer à un modèle de concurrence optimiste. Le framework d'entité le fait gratuitement, pas sûr de NHibernate. EF déclencherait une exception de concurrence optimiste si vos DELETEretours 0 lignes affectées.


1

Avez-vous essayé de déplacer la mise à jour des visites avant toute modification de visitItems? Ce verrou X devrait protéger les lignes "enfants".

Faire une trace acquise des verrous complets (et la conversion en lisible par l'homme) est beaucoup de travail mais pourrait montrer la séquence plus clairement.



-1

READ COMMITTED SNAPSHOT ON signifie que chaque transaction qui s'exécute dans READ COMMITTED ISOLATION LEVEL agira comme LECTURE COMMITTED SNAPSHOT.

Cela signifie que les lecteurs ne bloqueront pas les écrivains et les écrivains ne bloqueront pas les lecteurs.

Vous utilisez le niveau d'isolation des transactions en lecture répétable, c'est pourquoi vous avez un blocage. La lecture validée (sans instantané) conserve les verrous sur les lignes / pages jusqu'à la fin de l'instruction , mais la lecture répétable maintient les verrous jusqu'à la fin de la transaction .

Si vous regardez votre graphique Deadlock, vous pouvez voir un verrou "S" acquis. Je pense que c'est le verrou par le deuxième point -> "Lire tous les éléments de visite pour une visite donnée par VisitId."

  1. Changez votre niveau d'isolation de transaction de connexions NHibernate en lecture validée
  2. Vous devez analyser la requête pour votre 2e point et comprendre pourquoi il acquiert des verrous sur le PK si vous avez un index sur votre colonne visitID (cela peut être dû à l'absence de colonnes incluses dans votre index).
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.