SQL тупик на тому ж виключно заблокованому кластерному ключі (з NHibernate) при видаленні / вставці


29

Я працюю над проблемою тупикової ситуації вже досить декілька днів, і що б я не робив, воно так чи інакше зберігається.

По-перше, загальна передумова: у нас є відвідування з VisitItems у відносинах один на багато.

Відповідна інформація про VisitItems:

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)

Інформація про відвідування:

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]);

Кілька користувачів хочуть одночасно оновлювати таблицю VisitItems таким чином:

Окремий веб-запит створить відвідування з VisitItems (зазвичай це 1). Потім (запит проблеми):

  1. Заходить веб-запит, відкривається сесія NHibernate, починається транзакція NHibernate (з використанням повторного читання з увімкненою READ_COMMITTED_SNAPSHOT).
  2. Прочитайте всі пункти відвідування для даного візиту від VisitId .
  3. Код оцінює, чи елементи все ще є актуальними або якщо нам потрібні нові, використовуючи складні правила (настільки дещо тривалі, наприклад, 40 мс).
  4. Код знаходить 1 елемент потрібно додати, додавши його за допомогою NHibernate Visit.VisitItems.Add (..)
  5. Код визначає, що один елемент потрібно видалити (не той, який ми щойно додали), видаляє його за допомогою NHibernate Visit.VisitItems.Remove (item).
  6. Код здійснює транзакцію

За допомогою інструменту я моделюю 12 одночасних запитів, що, швидше за все, трапиться в майбутньому виробничому середовищі.

[EDIT] За запитом видалено багато деталей розслідування, які я додав сюди, щоб тримати це коротко.

Після багатьох досліджень наступним кроком було продумати спосіб, як я можу заблокувати підказку на інший індекс до того, який використовується у пункті де (тобто первинний ключ, оскільки він використовується для видалення), тому я змінив свою заяву блокування на :

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>();

Це зменшило частоти тупиків, але вони все ще відбувалися. І ось, з чого я починаю губитися:

Три ексклюзивні замки?

<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>

Слід від отриманої кількості запитів виглядає приблизно так.
[РЕДАКЦІЯ] Вау. Який тиждень. Зараз я оновив слід невідреагованим слідом відповідного твердження, яке, на мою думку, призводить до тупикової ситуації.

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

Тепер, схоже, мій замок має ефект, оскільки він відображається в графіку тупикового кута. Але що? Три ексклюзивні замки та один загальний замок? Як це працює на одному об’єкті / ключі? Я думав, що поки у вас є ексклюзивний замок, ви не можете отримати загальний замок від когось іншого? А навпаки. Якщо у вас є загальний замок, ніхто не може отримати ексклюзивний замок, їм доведеться чекати.

Я думаю, що мені не вистачає глибшого розуміння того, як працюють замки, коли їх переносять на кілька клавіш однієї таблиці.

Ось кілька речей, які я спробував, та їх вплив:

  • Доданий ще один натяк на індекс на IX_Visit_Id до оператора блокування. Без змін
  • Додано другий стовпець до IX_Visit_Id (ідентифікатор стовпця VisitItem); далеко не вдалося, але все-таки спробував. Без змін
  • Змінено рівень ізоляції назад, щоб прочитати скоєне (за замовчуванням у нашому проекті), тупики все ще відбуваються
  • Змінено рівень ізоляції на серіалізаційний. Тупики все ще відбуваються, але гірше (різні графіки). Я все одно не хочу цього робити.
  • Якщо взяти замок для столу, вони змушують їх піти (очевидно), але хто б хотів це зробити?
  • Беручи песимістичний блокування додатків (використовуючи sp_getapplock) працює, але це майже те саме, що і блокування таблиці, не хочу цього робити.
  • Додавання підказки READPAST до підказки XLOCK не мало значення
  • Я вимкнув PageLock на індексі та ПК, різниці немає
  • Я додав підказку ROWLOCK до підказки XLOCK, не маючи ніякої різниці

Деякі сторонні зауваження щодо NHibernate: те, як воно використовується, і я розумію, що це працює, - це те, що він кешує заяви sql, поки дійсно не вважає за потрібне їх виконувати, якщо тільки ви не викликаєте flush, чого ми намагаємося не робити. Тож більшість висловлювань (наприклад, ліниво завантажений агрегатний список VisitItems => Visit.VisitItems) виконуються лише за необхідності. Більшість фактичних заяв про оновлення та видалення з моєї транзакції виконуються наприкінці, коли транзакція здійснена (як це видно з сліду sql вище). Я дійсно не маю контролю над наказом про виконання; NHibernate вирішує, коли робити що. Моя початкова заява про блокування - це справді лише обхід.

Крім того, з твердженням про блокування я просто читаю елементи у невикористаному списку (я не намагаюся переосмислити список VisitItems об’єкта Visit, оскільки це не так, як NHibernate повинен працювати, наскільки я можу сказати). Тож хоч я спочатку прочитав список зі спеціальним висловом, NHibernate все одно завантажить цей список у свою колекцію проксі-об'єктів Visit.VisitItems, використовуючи окремий виклик sql, який я можу побачити в трасі, коли прийде час ліниво його десь завантажити.

Але це не має значення, правда? У мене вже є замок на вказаному ключі? Завантаження його ще раз це не змінить?

Як остаточне зауваження, можливо, для уточнення: Кожен процес спочатку додає власний візит із VisitItems, потім входить і змінює його (що ініціює видалення та вставлення та тупик). У моїх тестах жодного разу не змінюється такий самий візит або відвідування.

Хтось має ідею, як далі до цього підходити? Що-небудь, що я можу спробувати обійти цим розумним способом (без замків таблиці тощо)? Також я хотів би дізнатися, чому цей замок tripple-x можливий навіть на одному об’єкті. Я не розумію.

Будь ласка, дайте мені знати, чи потрібно більше інформації для вирішення головоломки.

[EDIT] Я оновив питання з DDL для двох задіяних таблиць.

Також мене попросили роз'яснити в очікуванні: Так, кілька тупиків тут і там добре, ми просто повторимо або змусимо користувача повторно подати (загалом кажучи). Але при нинішній частоті з 12 одночасними користувачами я б очікував, що там буде лише один кожні кілька годин. В даний час вони спливають кілька разів на хвилину.

На додаток до цього, я отримав додаткову інформацію про транзакцію = 2, яка може вказувати на проблему з вкладеними транзакціями, які ми насправді не використовуємо. Я теж буду досліджувати це і задокументувати результати.


2
Не використовуйте SELECT *. Це може стати вагомим фактором у ваших проблемах. Див stackoverflow.com/questions/3639861 / ...
JamieSee

Крім того, запустіть SELECT OBJECT_NAME(objectid, dbid) AS objectname, * FROM sys.dm_exec_sql_text(0x0200000024a9e43033ef90bb631938f939038627209baafb0000000000000000000000000000000000000000)sqlhandle на кожному кадрі ExecuStack для подальшого визначення того, що насправді виконується.
JamieSee

Можливо, ви стикаєтесь із хеш-зіткненням? dba.stackexchange.com/questions/80088/insert-only-deadlocks/…
Johnboy

Хлопці, боюся, я більше не є учасником цього проекту: - /, тому я не можу спробувати ваші пропозиції. Проте я передав нитку та всю інформацію деяким членам команди, щоб вони могли заглянути її замість мене.
Бен

Ви можете використати мою відповідь сценарію PowerShell на це запитання, щоб отримати більше деталей, що можуть вам допомогти. Зокрема, він отримає інформацію операторів SQL для ваших "невідомих" фреймів стеків. dba.stackexchange.com/questions/28996/…
JamieSee

Відповіді:


2

Я зробив кілька коментарів до цього ефекту, але я не впевнений, що ви отримаєте бажані результати, поєднуючи рівень ізоляції повторних читання транзакції з "Здійсненим знімком читання".

Повідомлення TIL у вашому списку тупикових ситуацій - це повторне зчитування, яке є навіть більш обмежуючим, ніж читання "Здійснено", і враховуючи описаний вами потік, ймовірно, призводить до тупикових ситуацій.

Можливо, ви можете намагатися зробити так, щоб ваш TIL DB залишався повторюваним читанням, але встановіть транзакцію, щоб явно використовувати TIL знімка з встановленим оператором рівня ізоляції транзакцій. Довідка: https://msdn.microsoft.com/en-us/library/ms173763.aspx Якщо так, я думаю, у вас має бути щось неправильне. Я не знайомий з nHibernate, але, схоже, тут є посилання: http://www.anujvarma.com/fluent-nhibernate-setting-database-transaction-isolation-level/

Якщо архітектура вашої програми дозволить це, можна спробувати прочитати зроблений знімок на рівні db, і якщо ви все ще отримаєте тупикові місця, увімкніть знімок з версією версій. Зверніть увагу, що якщо ви це зробите, вам потрібно переосмислити налаштування tempdb, якщо увімкнено знімок (версія версії) Я можу отримати вам всілякі матеріали щодо цього, якщо вам це потрібно - дайте мені знати.


2

У мене є пара думок. Перш за все, найпростіший спосіб уникнути тупиків - це завжди брати замки в одному порядку. Це означає, що різний код, що використовує явні транзакції, повинен отримувати доступ до об'єктів у тому самому порядку, але також доступ до рядків окремо за ключем у явній транзакції повинен бути відсортований за цим ключем. Спробуйте сортувати Visit.VisitItemsйого ПК , перш ніж робити Addабо , Deleteякщо це не величезна колекція в цьому випадку я б сортувати по SELECT.

Але сортування, ймовірно, тут не є вашою проблемою. Я здогадуюсь, 2 потоки захоплюють загальні блокування для всіх VisitItemIDs для даної теми, VisitIDа потік A не DELETEможе завершитися, поки потік B не випустить спільний замок, який він не буде, поки не DELETEзавершиться. Блокування додатків працюватиме тут і не так погано, як блокування таблиць, оскільки вони блокуються лише методом, а інші SELECTs працюватимуть чудово. Ви також можете взяти ексклюзивний замок на Visitстолі для даноїVisitID але знову ж таки, це може бути надмірним.

Я рекомендую перетворити жорстке видалення на м'яке ( UPDATE ... SET IsDeleted = 1замість використання DELETE) та очистити ці записи пізніше, масово, використовуючи деяку роботу з очищення, яка не використовує явних транзакцій. Це, очевидно, вимагатиме рефакторингу іншого коду для ігнорування цих видалених рядків, але це мій кращий метод для обробки DELETEs, включеного в SELECTявну транзакцію.

Ви також можете усунути SELECTтранзакцію та перейти на оптимістичну модель одночасності. Організаційна структура робить це безкоштовно, не впевнені в NHibernate. EF призведе до виключення оптимістичної конкурентоспроможності, якщо ваші DELETEприбутки вплинуть на 0 рядків.


1

Ви намагалися перенести оновлення Visits перед будь-якими модифікаціями посещатиІтем? Цей x-замок повинен захищати "дитячі" рядки.

Здійснення повного блокування набутого сліду (і перетворення на читабельне для людини) є великою роботою, але може показати послідовність більш чітко.


-1

Якщо ви не маєте поняття, чому стіл застряє, іноді трапляється тран

SET XACT_ABORT ON -> для цього слід попередити помилки, які спричиняють застрягнення транзиту. ПОЧАТИ TRAN TRAN_NAME --CODE для доступу до таблиці-- ЗАВДАННЯ TRAN TRAN_NAME

https://stackoverflow.com/questions/2277254/how-to-set-xact-abort-within-ado-net


-1

ЧИТАЙТЕ ЗАВАНТАЖЕНИЙ СНАПШОТ ВКЛЮЧЕНО означає, що кожна окрема транзакція, яка працює на РІЗНОЧЕНОМ РОЗВИТКУ ІЗОЛЯЦІЇ, буде діяти як ПРОЧИТАНО ЗДОРОВАНИЙ СНАПШОТ.

Це означає, що читачі не будуть блокувати письменників, а письменники не блокуватимуть читачів.

Ви використовуєте повторюваний рівень ізоляції транзакцій з читанням, тому у вас виникає тупик. Прочитане Виконане (без знімка) зберігає блокування у рядках / сторінках до кінця виписки , але повторне читання утримує блокування до кінця транзакції .

Якщо ви подивитеся на графік тупикового кута, ви зможете побачити придбаний замок "S". Я думаю, що це блокування другого пункту -> "Прочитайте всі пункти відвідування для даного відвідування VisitId."

  1. Змініть рівень ізоляції транзакцій підключень NHibernate на «Читає вчинено»
  2. Вам потрібно проаналізувати запит на вашу другу точку і зрозуміти, чому він отримує блокування на ПК, якщо у вас є індекс у колонці visitID (це може бути через відсутність включених стовпців у вашому індексі).
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.