Тупик на одном столе с замком 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
.
Без подробностей запроса не удастся выяснить точную причину ошибки. Как правило, вам нужно повторно учитывать ваши запросы, чтобы избежать таких тупиков, как
- Переместите запросы
SELECT
за пределы транзакций, чтобы он возвращал только зафиксированные данные, а не возвращал данные, содержащие изменения, которые могут откатываться. - Иногда вам нужно настроить запрос, чтобы SQL Server не нуждался в его параллельном распределении.
- Добавление подсказки
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