带有更新查询的同一个表上的SQL查询死锁

时间:2017-12-27 12:48:52

标签: sql sql-server stored-procedures deadlock

我在下面的存储过程中遇到了死锁错误 - UpdateTestEvents。

以下是xml死锁报告:

<deadlock>
 <victim-list>
  <victimProcess id="process1128b529468" />
 </victim-list>
 <process-list>
  <process id="process1128b529468" taskpriority="0" logused="0" waitresource="KEY: 7:72057594042777600 (fec90e3a2350)" waittime="2364" ownerId="158290173" transactionname="user_transaction" lasttranstarted="2017-12-17T01:20:45.553" XDES="0x1064ff98408" lockMode="U" schedulerid="9" kpid="6664" status="suspended" spid="57" sbid="2" ecid="0" priority="0" trancount="2" lastbatchstarted="2017-12-17T01:20:45.547" lastbatchcompleted="2017-12-17T01:20:45.543" lastattention="1900-01-01T00:00:00.543" clientapp="EntityFramework" hostname="STAAP8895" hostpid="3616" loginname="XLAPSDBScoring" isolationlevel="read committed (2)" xactid="158290173" currentdb="7" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
   <executionStack>
    <frame procname="analytics.dbo.UpdateTestEvents" line="25" stmtstart="1836" stmtend="2132" sqlhandle="0x030007005e4b4b2a97304c0155a5000001000000000000000000000000000000000000000000000000000000">
UPDATE dbo.History
SET Ignore = 0
WHERE Number = @Number
    AND dbo.StringsMatch(@candidate, ACType, DEFAULT) =    </frame>
   </executionStack>
   <inputbuf>
Proc [Database Id = 7 Object Id = 709577566]   </inputbuf>
  </process>
  <process id="process1127e522ca8" taskpriority="0" logused="301092" waitresource="KEY: 7:72057594043039744 (c41e1b4226b6)" waittime="2364" ownerId="158290165" transactionname="user_transaction" lasttranstarted="2017-12-17T01:20:45.447" XDES="0xf8dc5ff8a8" lockMode="U" schedulerid="2" kpid="4888" status="suspended" spid="60" sbid="2" ecid="0" priority="0" trancount="2" lastbatchstarted="2017-12-17T01:20:45.440" lastbatchcompleted="2017-12-17T01:20:45.437" lastattention="1900-01-01T00:00:00.437" clientapp="EntityFramework" hostname="STAAP1493" hostpid="3304" loginname="XLAPSDBScoring" isolationlevel="read committed (2)" xactid="158290165" currentdb="7" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
   <executionStack>
    <frame procname="analytics.dbo.UpdateTestEvents" line="32" stmtstart="2370" stmtend="3926" sqlhandle="0x030007005e4b4b2a97304c0155a5000001000000000000000000000000000000000000000000000000000000">
WITH ValidOriginsAndDestinations AS
(
      SELECT Origin FROM dbo.History
     WHERE Ignore = 0
        AND Number = @Number
     UNION ALL
         SELECT Destination FROM dbo.History
     WHERE Ignore = 0
        AND Number = @Number
)
UPDATE fh
SET Ignore = 0
FROM dbo.History AS fh
WHERE Number = @Number
    AND Ignore = 1
    AND 
    (       
        Origin IN (SELECT * FROM ValidOriginsAndDestinations)
        OR Destination IN (SELECT * FROM ValidOriginsAndDestinations)    </frame>
   </executionStack>
   <inputbuf>
Proc [Database Id = 7 Object Id = 709577566]   </inputbuf>
  </process>
 </process-list>
 <resource-list>
  <keylock hobtid="72057594042777600" dbid="7" objectname="analytics.dbo.History" indexname="PK_History" id="lockf50c41ac80" mode="X" associatedObjectId="72057594042777600">
   <owner-list>
    <owner id="process1127e522ca8" mode="X" />
   </owner-list>
   <waiter-list>
    <waiter id="process1128b529468" mode="U" requestType="wait" />
   </waiter-list>
  </keylock>
  <keylock hobtid="72057594043039744" dbid="7" objectname="xl_analytics_aviation.dbo.History" indexname="IX_History_Number" id="lock1128b5be680" mode="U" associatedObjectId="72057594043039744">
   <owner-list>
    <owner id="process1128b529468" mode="U" />
   </owner-list>
   <waiter-list>
    <waiter id="process1127e522ca8" mode="U" requestType="wait" />
   </waiter-list>
  </keylock>
 </resource-list>
</deadlock>

存储过程如下所示:

CREATE PROCEDURE [dbo].[UpdateTestEvents]
    @Number varchar(50)
AS
DECLARE @tolerance decimal(10,10) = 0.15
DECLARE @totalEvents decimal(10,0) = (SELECT COUNT(*) FROM dbo.History fh WHERE fh.Number = @Number)

IF(@totalEvents = 0) RETURN

DECLARE @candidate VARCHAR(50) = 
    (SELECT TOP 1 ACType
    FROM dbo.History AS fh
    WHERE fh.Number = @Number
    GROUP BY ACType
    HAVING (COUNT(*) / @totalEvents) > @tolerance
    ORDER BY MAX(ActualDepartureTime) DESC)
SELECT @candidate

UPDATE dbo.History
SET Ignore = 0
WHERE Number = @Number
    AND dbo.StringsMatch(@candidate, ACType, DEFAULT) = 1;


WITH ValidOriginsAndDestinations AS
(

    SELECT Origin FROM dbo.History
     WHERE Ignore = 0
        AND Number = @Number
     UNION ALL
     SELECT Destination FROM dbo.History
     WHERE Ignore = 0
        AND Number = @Number
)

UPDATE fh
SET Ignore = 0
FROM dbo.History AS fh
WHERE Number = @Number
    AND Ignore = 1
    AND 
    (
        Origin IN (SELECT * FROM ValidOriginsAndDestinations)
        OR Destination IN (SELECT * FROM ValidOriginsAndDestinations)
    );

WITH Comfirmeddt AS
(
    SELECT a.lat, a.long FROM dbo.places AS a
    JOIN dbo.History AS fh
    ON     a.tidentifier = fh.Origin
        OR a.tidentifier = fh.Destination    
    WHERE fh.Number = @Number
    GROUP BY a.tidentifier, a.lat, a.long
)
UPDATE fh
SET Ignore = 0
FROM dbo.History AS fh
JOIN dbo.places AS a
ON a.tidentifier = fh.Origin
    OR a.tidentifier = fh.Destination
WHERE fh.Ignore = 1
AND fh.Number = @Number
AND EXISTS
(
    SELECT * FROM Comfirmeddt AS confirmed   
    WHERE
    (
        a.lat  < confirmed.lat  + 0.5 AND a.lat  > confirmed.lat  - 0.5 AND
        a.long < confirmed.long + 0.5 AND a.long > confirmed.long - 0.5
    )
)

GO

我收到以下错误: 执行命令时发生错误。有关详细信息,请参阅内部异常事务(进程ID 57)在锁资源上与另一个进程死锁,并被选为死锁牺牲品。重新运行交易

[PK_History]的索引定义如下:

ALTER TABLE [dbo].[History] ADD  CONSTRAINT [PK_History] PRIMARY KEY CLUSTERED 
(
    [HashCode] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

主键是HashCode 有人可以建议我可以对此查询做些什么,以避免将来出现这种死锁。

请查看下面的表结构:

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

SET ANSI_PADDING ON
GO

CREATE TABLE [dbo].[History](
    [HashCode] [varchar](50) NOT NULL,
    [FaID] [varchar](50) NOT NULL,
    [Number] [varchar](255) NOT NULL,
    [ActualArrivalTime] [datetime] NULL,
    [ActualDepartureTime] [datetime] NULL,
    [ACType] [varchar](10) NULL,
    [Destination] [varchar](40) NULL,
    [DestinationCity] [varchar](100) NULL,  
    [Origin] [varchar](40) NULL,
    [Ignore] [bit] NOT NULL DEFAULT ((1)),
    [FlNumber] [varchar](255) NULL,
    [DateAdded] [datetime] NOT NULL DEFAULT (getdate()),
 CONSTRAINT [History] PRIMARY KEY CLUSTERED 
(
    [HashCode] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

SET ANSI_PADDING ON
GO

ALTER TABLE [dbo].[History]  WITH CHECK ADD  CONSTRAINT [FK_Number] FOREIGN KEY([Number])
REFERENCES [dbo].[Number] ([Number])
GO

ALTER TABLE [dbo].[History] CHECK CONSTRAINT [FK_Number]
GO

对存储过程的调用发生在.NET代码中,由实体框架实现。下面是从应用程序

调用此存储过程的框架
using (var db = new NumberDbContext())
            {
foreach (var tn in Numbers)
            {
 db.UpdateTestEvents(tailNumber);
}
}

请查看以下查询的执行计划: https://www.brentozar.com/pastetheplan/?id=B1dGzUMQf

同样定义IX_History_Number索引:

CREATE NONCLUSTERED INDEX [IX_History_Number] ON [dbo].[History]
(
    [Number] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

2 个答案:

答案 0 :(得分:0)

死锁是由于查询使用不同的索引来访问同一行。避免此死锁条件的一种方法是使用application lock序列化对相同@Number值的访问,直到提交事务为止。请注意,必须使用Transaction锁拥有者的显式事务调用proc。

CREATE PROCEDURE [dbo].[UpdateTestEvents]
    @Number varchar(50)
AS
DECLARE @return_code int;
EXEC @return_code = sp_getapplock @Resource = @Number, @LockMode = 'Exclusive', @LockOwner = 'Transaction';
IF @return_code NOT IN(0,1)
BEGIN
    RAISERROR('Unexpected sp_getapplock return code is %d',16,1,@return_code);
    RETURN @return_code;
END;
--remainder of proc code here
GO

答案 1 :(得分:-1)

我首先检查数据库中的隔离级别,如果启用了快照隔离,则不太​​可能在数据库中获取共享锁。

select name
        , s.snapshot_isolation_state
        , snapshot_isolation_state_desc
        , is_read_committed_snapshot_on
        , recovery_model
        , recovery_model_desc
        , collation_name
    from sys.databases s

为了避免在执行查询时发生死锁,最好在这种情况下发出nolock,尽管有些人认为它会导致脏读,每次更新语句都使用begin和end或使用try block。< / p>

CREATE PROCEDURE [dbo].[UpdateTestEvents] @Number VARCHAR(50)
AS
BEGIN
    DECLARE @tolerance DECIMAL(10, 10) = 0.15
    DECLARE @totalEvents DECIMAL(10, 0) = (
            SELECT COUNT(*)
            FROM dbo.History fh(NOLOCK)
            WHERE fh.Number = @Number
            )

    IF (@totalEvents = 0)
    BEGIN
        DECLARE @candidate VARCHAR(50) = (
                SELECT TOP 1 ACType
                FROM dbo.History(NOLOCK) AS fh
                WHERE fh.Number = @Number
                GROUP BY ACType
                HAVING (COUNT(*) / @totalEvents) > @tolerance
                ORDER BY MAX(ActualDepartureTime) DESC
                )

        --SELECT @candidate
        UPDATE dbo.History
        SET Ignore = 0
        WHERE Number = @Number
            AND dbo.StringsMatch(@candidate, ACType, DEFAULT) = 1;

        BEGIN
            WITH ValidOriginsAndDestinations
            AS (
                SELECT Origin
                FROM dbo.History(NOLOCK)
                WHERE Ignore = 0
                    AND Number = @Number

                UNION ALL

                SELECT Destination
                FROM dbo.History(NOLOCK)
                WHERE Ignore = 0
                    AND Number = @Number
                )
            UPDATE fh
            SET Ignore = 0
            FROM dbo.History AS fh
            WHERE Number = @Number
                AND Ignore = 1
                AND (
                    Origin IN (
                        SELECT *
                        FROM ValidOriginsAndDestinations
                        )
                    OR Destination IN (
                        SELECT *
                        FROM ValidOriginsAndDestinations
                        )
                    )
        END

        BEGIN
            WITH AirportsWithConfirmedFlights
            AS (
                SELECT a.lat
                    ,a.long
                FROM dbo.places AS a
                INNER JOIN dbo.History AS fh ON a.tidentifier = fh.Origin
                    OR a.tidentifier = fh.Destination
                WHERE fh.Number = @Number
                GROUP BY a.tidentifier
                    ,a.lat
                    ,a.long
                )
            UPDATE fh
            SET Ignore = 0
            FROM dbo.History AS fh
            INNER JOIN dbo.places AS a ON a.tidentifier = fh.Origin
                OR a.tidentifier = fh.Destination
        END
    END
END

`

  • Nolock`和Bad practice

    各种文档,建议停止使用nolock提示的最佳实践,因为这可能导致脏读和其他影响。但是,如果我们通过使用nolock提示进行测试以及性能和影响,并且有充分的文档证明它们非常有用。最好的工作 这是为了改变数据库中的隔离级别。这个aswel有几个注意事项。