更新同一张表时MS SQL Server中出现死锁

时间:2019-02-19 05:58:22

标签: sql-server tsql deadlock dynamic-sql

我的环境中遇到太多死锁问题。死锁主要发生在尝试升级时。我已经安装了SentryOne,以了解死锁会发生什么。.我们的系统非常复杂,并且同一张桌子上有很多死锁。

我的事务级别是可重复读取,并且我在同一张表上遇到循环死锁。 我可以采取哪些步骤来避免循环死锁。 我们如何确保代码中不会发生循环死锁。

下面显示了对保留indexname =“ IX_Reservations_ReservationId_OrganizationId的键锁。我在resut集中确实有18k记录,并且似乎应用了索引编制。但是显示对ReservationOrgananization索引的索引扫描。您认为case语句是索引的原因吗我在使用插入,更新删除的事务中对情况进行了很多选择

enter image description here enter 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 :(得分:1)

注意:我正在用手机回答。因此没有图表。

这是一个同步问题。

当两个或多个进程想要访问同一记录,页或表(取决于锁的粒度和锁的升级)但顺序不同时,通常会发生死锁,尤其是在涉及显式事务的情况下,尤其是在系统重负荷下。

在事务中说,过程A更新记录1,然后尝试更新记录2。

同时,进程B在事务中更新记录2,然后尝试读取或更新记录1。A更改了记录,因此B甚至在A的事务提交或回滚之前都无法读取它。同样,A无法读取记录B已锁定,并且繁荣,僵局。

“我知道”,你是对自己说。

是的,但是我要讲的是操作顺序问题。您提到了该系统非常复杂,这有点白费。这是一个同步问题。您知道系统中的不同代码路径是否以不同的顺序运行相同或相似的查询吗?

在运行正常的系统中,如果进程A更新记录1,然后更新记录2,并且进程B以相同的顺序运行这些相同的操作,则请求将排队并连续运行。 B将等待A完成,假定未超过超时时间。

之所以可行,是因为B永远不会在A仍保持对记录1的锁定的情况下获得对记录2的锁定,因为B希望像A一样从记录1开始,并且直到A释放它都无法获取它。

如果您要更新大范围的记录或在隔离级别设置为可序列化的事务中进行大量读取,则可能要使用表锁而不是记录锁。

>

答案 1 :(得分:1)

您在这里有2个问题:

  1. 您的表没有indexes,至少没有那些可以在同一事务中执行但未被select捕获的deadlock graph中使用的那些。

  2. p>
  3. 您的交易使用repeatable read隔离级别。

使用repeatable read时,获取的shared locks会保留到transaction的结尾。

在您的会话尝试进行update(会话1)和插入(会话2)之前,他们进行了一些选择,从而锁定了整个表,它没有被输入缓冲区捕获,但是您可能知道先前数据执行了什么代码修改。

两个会话都在整个表上保留S-lock,并且想要update /'insert',因此它们需要将S-lock转换为IX,作为该{{ 1}}将被更新/插入,table应该放在桌子上。

intent lockIX不兼容,因此第一个会话在S上释放其S-lock时会等待第二个会话,但是第二个会话无法提交,因为它也不能table,因为它在同一张表上需要insert,并且由于第一个会话拥有IX,因此无法授予它。

要解决此问题,您应该找到先前的S-lock并创建select,以便只锁定某些行,而不是整个indexes,否则您应该摆脱{{ 1}}。

您可以通过以下方式自己复制:

打开2个SSMS查询窗口,然后首先在table中进行选择以锁定整个表,我使用repeatable read进行了选择:

repeatable read

此时,两个会话都将tablock放在set transaction isolation level repeatable read begin tran select count(*) from dbo.t with (tablock); 上。 现在返回第一个窗口并尝试进行更新:

S-lock

此查询将被阻止,因为它需要无法授予的IX, 您可以使用此代码查看table的情况(也许您应该过滤会话ID,我在没有活动的专用服务器上进行了测试):

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

enter image description here

现在,第二个会话尝试插入时,就会发生死锁:

lock

enter image description here