多用户执行SP时死锁

时间:2016-05-19 13:45:06

标签: sql-server stored-procedures deadlock

我们有一个存储过程正在插入/更新/删除项目资源文件,我们遇到一个问题,用户有时会遇到死锁,我相信是因为另一个用户正在使用存储过程。
deadlocking

SP

  ALTER PROCEDURE [file].[usp_iudItemResourceFile]
    @p_ItemID INT
    , @p_ID INT = NULL OUTPUT
    , @p_IsDefault BIT = NULL
    , @p_Description NVARCHAR ( MAX ) = NULL

    --, @p_RelPath NVARCHAR ( 2000 ) = NULL
    , @p_Name NVARCHAR ( 250 ) = NULL
    , @p_sequenceNumberID INT = NULL
    , @p_Type TINYINT = NULL
    , @p_Size INT = NULL
    , @p_Status TINYINT = NULL
    , @p_DoerTicket VARCHAR ( 200 ) = NULL
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON;

    DECLARE @doerUserID INT
            , @doerCompanyID INT
    EXEC system.usp_validateAuthenticationTicket @p_Ticket = @p_DoerTicket
                                                , @p_UserID = @doerUserID OUTPUT
                                                , @p_CompanyID = @doerCompanyID OUTPUT


    BEGIN TRANSACTION

    IF ( @p_ID < 0 )
    BEGIN
        PRINT 'TBD'
    END

    DECLARE @res INT
    EXEC @res = [file].usp_iudResourceFile @p_ID = @p_ID OUTPUT
                                    --, @p_RelPath = @p_RelPath
                                    , @p_Name = @p_Name
                                    , @p_Type = @p_Type
                                    , @p_Size = @p_Size
                                    , @p_Status = @p_Status
                                    , @p_DoerTicket = @p_DoerTicket

    IF ( ( @@ERROR <> 0 ) OR ( @res <> 0 ) )    
    BEGIN
        IF ( @@TRANCOUNT > 0 ) ROLLBACK TRANSACTION
        RETURN 1050
    END

    IF ( ( @p_ID > 0 ) AND ( @p_IsDefault = 1 ) )
    BEGIN
        UPDATE [file].ItemResourceFile SET
            IsDefault = 0
        WHERE Item_ID = @p_ItemID

        IF ( @@ERROR <> 0 ) 
        BEGIN
            IF ( @@TRANCOUNT > 0 ) ROLLBACK TRANSACTION
            RETURN 1053
        END
    END

    MERGE INTO [file].ItemResourceFile AS target
    USING ( SELECT @p_ItemID
                    , @p_ID
                    , @p_IsDefault
                    , @p_Description
                    , @p_Status
                    , @p_sequenceNumberID
                    , @doerUserID
                    , @doerCompanyID ) AS source ( ItemID, ResourceFileID, IsDefault, Description, Status, sequenceID, DoerUserID, DoerCompanyID )
        ON ( target.Item_ID = source.ItemID )
            AND ( target.ResourceFile_ID = source.ResourceFileID )
        WHEN MATCHED THEN
            UPDATE SET 
                target.Description = NULLIF ( ISNULL ( source.Description, target.Description ), N'' )
                , target.IsDefault = ISNULL ( source.IsDefault, target.IsDefault )
                , target.Status = ISNULL ( source.Status, target.Status )
                , target.sequenceID = source.sequenceID
                , target.LastModifierUser_ID = source.DoerUserID
                , target.DateTimeModified = GETUTCDATE ( )
        WHEN NOT MATCHED BY TARGET AND source.ResourceFileID > 0 THEN
            INSERT ( Item_ID
                    , ResourceFile_ID
                    , Description
                    , IsDefault
                    , Status
                    , sequenceID
                    , CreatorUser_ID
                    , LastModifierUser_ID )
                VALUES ( source.ItemID
                        , source.ResourceFileID
                        , NULLIF ( source.Description, N'' )
                        , ISNULL ( source.IsDefault, 0 )
                        , ISNULL ( source.Status, 0 ) --0: Active
                        , source.sequenceID
                        , source.DoerUserID
                        , source.DoerUserID
                        )
        WHEN NOT MATCHED BY SOURCE 
                AND target.Item_ID = @p_ItemID
                AND target.ResourceFile_ID = ABS ( @p_ID ) THEN
            DELETE;

    IF ( @@ROWCOUNT <> 1 )
    BEGIN
        IF ( @@TRANCOUNT > 0 ) ROLLBACK TRANSACTION
        RAISERROR ( 'DBException_ItemNotFound', 16, 1 )
        RETURN 1060
    END

    --IF ( @p_ID IS NULL )
    --  SET @p_ID = SCOPE_IDENTITY ( )

    COMMIT TRANSACTION

    RETURN 0
END

如何修复它以便多个用户可以使用它或找出真正触发它的内容

1 个答案:

答案 0 :(得分:1)

您的SP基本上是这样做的:

  1. 更新表
  2. SELECT FROM Table
  3. 更新表或INSERT表
  4. 所有发生在同一交易中(其中2 + 3 = MERGE)

    当两个实例同时运行语句1时,可能会发生死锁情况。这两个实例都不能执行语句2,因为表中的某些行被另一个实例中的更新锁定。

    避免在具有多个更新的一个表上发生死锁的方法是确保不同的实例不会尝试读取可被其他实例锁定的行。常见错误是通过在WHERE子句中使用非索引列或聚合(例如MAX)来导致表扫描。

    此查询可以为您(有点神秘)详细说明发生死锁的位置

    WITH XmlData AS (
        SELECT CONVERT(xml, [target_data]) AS Target_Data
        FROM sys.dm_xe_session_targets AS xt
             INNER JOIN sys.dm_xe_sessions AS xs
                 ON xs.address = xt.event_session_address
        WHERE xs.name = N'system_health'
              AND xt.target_name = N'ring_buffer'
    )
    SELECT xed.value('@timestamp', 'datetime') as Creation_Date,
           xed.query('.') AS Extend_Event
    FROM XmlData 
         CROSS APPLY Target_Data.nodes('RingBufferTarget/event[@name="xml_deadlock_report"]') AS XEventData(xed)
    ORDER BY Creation_Date DESC