Тупик в MS SQL Server при обновлении той же таблицы - PullRequest
0 голосов
/ 19 февраля 2019

У меня слишком много проблем с тупиком в моей среде.В основном тупик случается при попытке обновления.Я установил SentryOne, чтобы знать, что происходит с взаимоблокировкой. У нас очень сложная система, и мы получаем множество взаимоблокировок на одном столе.

Мой уровень транзакции повторяемый. Чтение, и я получаю циклические блокировки в одной таблице.Какие шаги я могу предпринять, чтобы избежать циклических тупиков.Как сделать так, чтобы в коде не возникало циклического тупика.

Ниже показана блокировка ключа для Reservations indexname = "IX_Reservations_ReservationId_OrganizationId. У меня 18k записей в наборе результатов, и индексация, кажется, была применена. Нопоказ сканирования индекса по индексу ReservationOrgananization. Как вы думаете, оператор запроса является причиной сканирования индекса. Я делаю много операций выбора с условием регистра внутри транзакции, в которой используется вставка, удаление обновления

enter image description hereenter image description here

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[RowVersion] AS [RowVersion], 
[Extent1].[AdjustmentAmount] AS [AdjustmentAmount], 
[Extent1].[Comment] AS [Comment], 
[Extent1].[ReservationAdjustment_Reservation] AS [ReservationAdjustment_Reservation], 
[Extent1].[ReservationAdjustment_Promotion] AS [ReservationAdjustment_Promotion], 
[Extent1].[ReservationAdjustment_AdjustmentReason] AS [ReservationAdjustment_AdjustmentReason], 
[Extent1].[CreatedBy] AS [CreatedBy], 
[Extent1].[CreatedOn] AS [CreatedOn], 
[Extent1].[ReservationProductId] AS [ReservationProductId]
FROM  [dbo].[ReservationAdjustments] AS [Extent1]
INNER JOIN [dbo].[Reservations] AS [Extent2] ON [Extent1].[ReservationAdjustment_Reservation] = [Extent2].[Id]
WHERE 123 = (CASE WHEN ([Extent2].[OrganizationId] = @p__linq__0) THEN [Extent2].[Id] END)

<deadlock>
<victim-list>
    <victimProcess id="process23b4b08c8" />
</victim-list>
<process-list>
    <process id="process23b4b08c8" taskpriority="0" logused="0" waitresource="OBJECT: 6:30675207:0 " waittime="3311" ownerId="55794405" transactionname="user_transaction" lasttranstarted="2019-02-20T16:41:15.963" XDES="0x2a4125770" lockMode="IX" schedulerid="1" kpid="102820" status="suspended" spid="94" sbid="2" ecid="0" priority="0" trancount="2" lastbatchstarted="2019-02-20T16:41:16.403" lastbatchcompleted="2019-02-20T16:41:16.390" lastattention="1900-01-01T00:00:00.390" clientapp=".Net SqlClient Data Provider" hostname="''"" ''" loginname="''"" isolationlevel="repeatable read (3)" xactid="55794405" currentdb="6" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
        <frame procname="adhoc" line="1" stmtstart="234" stmtend="670" sqlhandle="0x0200000066d3ee34a3aa9027d9cf2157cf5cca17470f03dd0000000000000000000000000000000000000000">
unknown    </frame>
        <frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
unknown    </frame>
    </executionStack>
    <inputbuf>
(@0 decimal(18,2),@1 decimal(18,2),@2 decimal(18,2),@3 bit,@4 nvarchar(255),@5 datetimeoffset(7),@6 int,@7 binary(8))update [dbo].[P]
set [TotalPrice] = @0, [PassengerTaxAndFees] = @1, [AgentCommission] = @2, [SupplierChangeExists] = @3, [ModifiedBy] = @4, [Modified] = @5
where (([Id] = @6) and ([RowVersion] = @7))
select [RowVersion]
from [dbo].[P]
where @@ROWCOUNT &gt; 0 and [Id] = @6   </inputbuf>
    </process>
    <process id="process294aedc28" taskpriority="0" logused="12548" waitresource="OBJECT: 6:30675207:0 " waittime="6588" ownerId="55792892" transactionname="user_transaction" lasttranstarted="2019-02-20T16:41:12.020" XDES="0x2c5e09770" lockMode="IX" schedulerid="2" kpid="49456" status="suspended" spid="80" sbid="2" ecid="0" priority="0" trancount="2" lastbatchstarted="2019-02-20T16:41:13.127" lastbatchcompleted="2019-02-20T16:41:13.123" lastattention="1900-01-01T00:00:00.123" clientapp=".Net SqlClient Data Provider" hostname="''"" ''" loginname="''"" isolationlevel="repeatable read (3)" xactid="55792892" currentdb="6" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
        <frame procname="adhoc" line="1" stmtstart="1062" stmtend="5572" sqlhandle="0x02000000328db70a915f43baef23378214e51d7e0cacc8c50000000000000000000000000000000000000000">
unknown    </frame>
        <frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
unknown    </frame>
    </executionStack>
    <inputbuf>
(@0 datetime2(7),@1 datetime2(7),@2 int,@3 decimal(18,2),@4 bit,@5 decimal(18,2),@6 bit,@7 bit,@8 nvarchar(255),@9 int,@10 decimal(18,2),@11 nvarchar(255),@12 nvarchar(25),@13 nvarchar(max) ,@14 nvarchar(255),@15 decimal(18,2),@16 nvarchar(25),@17 datetime2(7),@18 nvarchar(max) ,@19 decimal(18,2),@20 bit,@21 int,@22 int,@23 bit,@24 bit,@25 bit,@26 bit,@27 bit,@28 bit,@29 nvarchar(25),@30 bit,@31 bit,@32 nvarchar(255),@33 datetimeoffset(7),@34 nvarchar(255),@35 datetimeoffset(7),@36 int,@37 int,@38 int,@39 int,@40 int,@41 int)insert [dbo].[P]([EndDate], [StartDate], [Quantity], [PriceEach], [TotalPrice], [Comments], [NetRate], [NetRateAmountDue], [NetAmountPaid], [NetAmountPaidDate], [NetRatePaidInFull], [CommissionPaidInFull], [Owner], [Organization], [Source], [SourceConfirmationNumber], [PassengerTaxAndFees], [PassengerTaxesAndFeesDescription], [Destination], [ProductType], [Description], [RateDe   </inputbuf>
    </process>
</process-list>
<resource-list>
    <objectlock lockPartition="0" objid="30675207" subresource="FULL" dbid="6" objectname="''.dbo.P" id="lock2ab07a480" mode="S" associatedObjectId="30675207">
    <owner-list>
        <owner id="process294aedc28" mode="S" />
        <owner id="process294aedc28" mode="IX" requestType="convert" />
    </owner-list>
    <waiter-list>
        <waiter id="process23b4b08c8" mode="IX" requestType="convert" />
    </waiter-list>
    </objectlock>
    <objectlock lockPartition="0" objid="30675207" subresource="FULL" dbid="6" objectname=".dbo.''"" id="lock2ab07a480" mode="S" associatedObjectId="30675207">
    <owner-list>
        <owner id="process23b4b08c8" mode="S" />
        <owner id="process23b4b08c8" mode="IX" requestType="convert" />
    </owner-list>
    <waiter-list>
        <waiter id="process294aedc28" mode="IX" requestType="convert" />
    </waiter-list>
    </objectlock>
</resource-list>
</deadlock>

Ответы [ 2 ]

0 голосов
/ 22 февраля 2019

У вас есть 2 проблемы:

  1. В вашей таблице нет indexes, по крайней мере, тех, которые могут быть использованы в некоторых select, которые были выполнены в той же транзакции, но не былиdeadlock graph.

  2. Ваши транзакции используют repeatable read уровень изоляции.

При использовании repeatable read, shared locks полученные удерживаются до конца transaction.

Прежде чем ваши сеансы попытаются сделать update (сеанс 1) и вставить (сеанс 2), они делают выбор, который заблокировал всю таблицу, этоне перехватывается во входном буфере, но, возможно, вы знаете, какой код был выполнен до изменения данных.

Оба сеанса содержат S-lock на всей таблице и хотят update / 'insert', поэтому им нужно преобразовать S-lockв IX, так как некоторые строки в этом table будут обновлены / вставлены, а intent lock следует поместить в таблицу.

IX несовместимо с S, поэтому первый сеанс ожидаетвторой сеанс, когда он выпускает S-lock на table, но второй session не может зафиксировать, потому что он также не может insert, так как ему нужно IX для той же таблицы, и он не может быть предоставлен, так как первый сеанс содержит S-lock.

Чтобы исправить это, вы должны найти предыдущийselect и создайте indexes, который позволит заблокировать только некоторые строки, а не целые table, или вы должны избавиться от repeatable read.

Вы можете воспроизвести это самостоятельно таким образом:

открыть 2 окна запросов SSMS и сначала сделать выбор в repeatable read, который заблокирует всю таблицу, я делаю это с помощью tablock:

set transaction isolation level repeatable read
begin tran 

select count(*)
from dbo.t with (tablock);

На данный момент обе сессии удерживают S-lock на table.Теперь вернитесь в первое окно и попробуйте сделать обновление:

update dbo.t
set col = 'bbb'
where id = 10;

Этот запрос будет заблокирован, так как ему нужен IX, который не может быть предоставлен, вы можете увидеть ситуацию lock, используя этот код (возможно, вам следуетФильтр для идентификаторов вашей сессии, я делаю свой тест на выделенном сервере без активности):

select resource_type,
       request_session_id,
       resource_associated_entity_id,
       request_mode,
       request_type,
       request_status
from sys.dm_tran_locks
where resource_type not in ('DATABASE', 'METADATA');

enter image description here

Теперь, как только вторая сессия пытаетсявставка, возникает тупик:

insert into dbo.t (id, col)
values(3, 'aaa');

enter image description here

0 голосов
/ 19 февраля 2019

Предостережение: я отвечаю на это по телефону.Так что никаких диаграмм.

Это проблема синхронизации.

Обычно возникает взаимоблокировка, когда два или более процесса хотят получить доступ к одной и той же записи, странице или таблице (в зависимости от степени детализации и повышения блокировки), но вдругой порядок, особенно если задействованы явные транзакции, и особенно если система находится под большой нагрузкой.

Скажем, процесс A в транзакции обновляет запись 1, затем пытается обновить запись 2.

Между тем, процесс B в транзакции обновляет запись 2, затем пытается прочитать или обновить запись 1. A изменил запись, поэтому B даже не может прочитать ее, пока транзакция A не завершится или не откатится.Точно так же, A не может прочитать запись, которую B заблокировал, и бум, тупик.

«Я знал это», - говорите вы себе.

Ну да, но что яНахожу это в порядке операции вопроса.Вы упомянули, что система очень сложна, и это является чем-то вроде дешевой распродажи.Это проблема синхронизации.Знаете ли вы, если разные пути кода в системе выполняют одинаковые или похожие запросы в другом порядке?

В исправной системе процесс A обновляет запись 1, затем обновляет запись 2, а процесс B выполняет этите же операции в том же порядке, запросы будут поставлены в очередь и будут выполняться последовательно.B будет ждать окончания A, предполагая, что тайм-аут не превышен.

Это работает, потому что B никогда не получит блокировку для записи 2, пока A все еще удерживает блокировку для записи 1, потому что B хочет начатьс записью 1, такой же, как А, и не может получить ее, пока ее не выпустит.

Вы можете иметь дело с блокировками таблиц вместо блокировок записей, если вы обновляете большие диапазоны записей или выполняете много операций чтения в своих транзакцияхс вашим уровнем изоляции, установленным на сериализуемость и т. д. Применяются те же общие принципы.

...