Тупик на одном столе с замком X и U

У меня есть хранимая процедура со следующими двумя транзакциями в Begin и Commit tran.

UPDATE  mytable
SET     UserID = @ToUserID
WHERE   UserID = @UserID 

DELETE  FROM mytable
WHERE   UserID = @UserID 

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

<deadlock-list>
    <deadlock victim="process16409057468">
        <process-list>
            <process id="process16409057468" taskpriority="0" logused="912" waitresource="RID: 6:1:2392:152" waittime="3022" ownerId="6283339" transactionname="user_transaction" lasttranstarted="2019-02-08T21:08:24.663" XDES="0x16401b98490" lockMode="U" schedulerid="8" kpid="23924" status="suspended" spid="92" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2019-02-08T21:08:24.667" lastbatchcompleted="2019-02-08T21:08:24.667" lastattention="1900-01-01T00:00:00.667" clientapp=".Net SqlClient Data Provider" hostname="GYAAN" hostpid="5624" loginname="sa" isolationlevel="read uncommitted (1)" xactid="6283339" currentdb="6" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
                <executionStack>
                    <frame procname="mytable" line="377" stmtstart="33320" stmtend="33540" sqlhandle="0x030006004f6bf63211085201eaa9000001000000000000000000000000000000000000000000000000000000">
                        UPDATE  mytable
                        SET     UserID = @ToUserID
                        WHERE   UserID = @UserID      
                    </frame>
                </executionStack>
                <inputbuf>
                    Proc [Database Id = 6 Object Id = 855010127]    
                </inputbuf>
            </process>
            <process id="process163feab3088" taskpriority="0" logused="912" waitresource="RID: 6:1:2392:149" waittime="99" ownerId="6282851" transactionname="user_transaction" lasttranstarted="2019-02-08T21:08:22.107" XDES="0x16401b20490" lockMode="U" schedulerid="3" kpid="33220" status="suspended" spid="81" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2019-02-08T21:08:22.103" lastbatchcompleted="2019-02-08T21:08:22.107" lastattention="1900-01-01T00:00:00.107" clientapp=".Net SqlClient Data Provider" hostname="GYAAN" hostpid="5624" loginname="sa" isolationlevel="read uncommitted (1)" xactid="6282851" currentdb="6" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
                <executionStack>
                    <frame procname="mytable" line="382" stmtstart="33650" stmtend="33848" sqlhandle="0x030006004f6bf63211085201eaa9000001000000000000000000000000000000000000000000000000000000">
                        DELETE  FROM mytable
                        WHERE   UserID = @UserID     
                    </frame>
                </executionStack>
                <inputbuf>
                    Proc [Database Id = 6 Object Id = 855010127]    
                </inputbuf>
            </process>
        </process-list>

        <resource-list>
            <ridlock fileid="1" pageid="2392" dbid="6" objectname="mytable" id="lock164096b7800" mode="X" associatedObjectId="72057594051493888">
                <owner-list>
                    <owner id="process163feab3088" mode="X"/>
                </owner-list>
                <waiter-list>
                    <waiter id="process16409057468" mode="U" requestType="wait"/>
                </waiter-list>
            </ridlock>
            <ridlock fileid="1" pageid="2392" dbid="6" objectname="mytable" id="lock163f7fb2c80" mode="X" associatedObjectId="72057594051493888">
                <owner-list>
                    <owner id="process16409057468" mode="X"/>
                </owner-list>
                <waiter-list>
                    <waiter id="process163feab3088" mode="U" requestType="wait"/>
                </waiter-list>
            </ridlock>
        </resource-list>
    </deadlock>
</deadlock-list>

Может кто-нибудь объяснить, как и что могло вызвать мертвый замок?

Я испытываю трудности с пониманием этого потока блокировки X и U в данный момент.

Не могли бы вы объяснить, что могло произойти, когда X и U застряли между мертвой блокировкой?

Ответ 1

Вы не предоставили достаточных деталей ваших запросов, но график взаимоблокировок, которым вы поделились, ясно показывает, что это взаимоблокировка "писатель-писатель" из-за параллелизма, поскольку все предоставленные или запрошенные блокировки - это X или U.

<resource-list>
    <ridlock fileid="1" pageid="2392" dbid="6" objectname="xx" id="lock164096b7800" mode="X" associatedObjectId="72057594051493888">
        <owner-list>
            <owner id="process163feab3088" mode="X"/>
        </owner-list>
        <waiter-list>
            <waiter id="process16409057468" mode="U" requestType="wait"/>
        </waiter-list>
    </ridlock>
    <ridlock fileid="1" pageid="2392" dbid="6" objectname="mytable" id="lock163f7fb2c80" mode="X" associatedObjectId="72057594051493888">
        <owner-list>
            <owner id="process16409057468" mode="X"/>
        </owner-list>
        <waiter-list>
            <waiter id="process163feab3088" mode="U" requestType="wait"/>
        </waiter-list>
    </ridlock>
</resource-list>

Одна из важных особенностей взаимоблокировок писатель-писатель состоит в том, что SQL Server удерживает монопольные блокировки до фиксации транзакции, в отличие от разделяемых блокировок, которые по умолчанию на уровне фиксации read committed.

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

  1. Переместите запросы SELECT за пределы транзакций, чтобы он возвращал только зафиксированные данные, а не возвращал данные, содержащие изменения, которые могут откатываться.
  2. Иногда вам нужно настроить запрос, чтобы SQL Server не нуждался в его параллельном распределении.
  3. Добавление подсказки MAXDOP к запросу, чтобы принудительно запустить его последовательно, удалит любое изменение тупика параллелизма внутри запроса.

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

Чтобы решить эту проблему, вы можете выбрать записи с помощью WITH (SERIALIZABLE) как WITH (SERIALIZABLE) ниже

UPDATE  mytable WITH (SERIALIZABLE)
SET     UserID = @ToUserID
WHERE   UserID = @UserID

Это примет необходимую блокировку обновления для записи и остановит другой процесс для получения любой блокировки (общей/исключительной) для записи и предотвратит любые взаимоблокировки.

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

Ответ 2

За исключением того факта, что вы, например, обновляете, а затем удаляете ту же запись, что выглядит неправильно, вы можете получить все необходимые xlocks перед выполнением DDL.

select UserID
FROM mytable   with(xlock, holdlock, rowlock)
WHERE UserID  in (@ToUserID, @UserID)

UPDATE  mytable
SET     UserID = @ToUserID
WHERE   UserID = @UserID 

DELETE  FROM mytable
WHERE   UserID = @UserID