Gostaria de pedir ajuda à comunidade para este cenário de impasse:
<deadlock-list>
<deadlock victim="process17392264e8">
<process-list>
<process id="process17392264e8" taskpriority="5" logused="0" waitresource="OBJECT: 7:2018106230:0 " waittime="4449" ownerId="394509589" transactionname="user_transaction" lasttranstarted="2019-06-05T12:06:41.320" XDES="0x18ed9516f0" lockMode="IX" schedulerid="4" kpid="8348" status="suspended" spid="143" sbid="0" ecid="0" priority="-5" trancount="2" lastbatchstarted="2019-06-05T12:06:41.310" lastbatchcompleted="2019-06-05T12:06:41.310" lastattention="1900-01-01T00:00:00.310" clientapp="Helios Orange" hostname="???" hostpid="508" loginname="???" isolationlevel="read committed (2)" xactid="394509589" currentdb="7" currentdbname="XXXXXX" lockTimeout="4294967295" clientoption1="673384544" clientoption2="128568">
<executionStack>
<frame procname="XXXXXX.dbo.hp_ZdvojeniKonstrukceATech" line="73" stmtstart="6876" stmtend="7250" sqlhandle="0x03000700efbf9d27f8924d01e9a8000001000000000000000000000000000000000000000000000000000000">
UPDATE CZ SET IntPermanentniZmena=0
FROM TabDavka D
INNER JOIN TabCZmeny CZ ON (CZ.ID=D.ZmenaOd AND CZ.IntPermanentniZmena=1)
WHERE D.IDDilce=@IDKusovni </frame>
<frame procname="adhoc" line="2" stmtstart="44" stmtend="152" sqlhandle="0x010007000f5ba906f09b4b6f0700000000000000000000000000000000000000000000000000000000000000">
EXEC @ret=hp_ZdvojeniKonstrukceATech 120234, 39008, 1 </frame>
</executionStack>
<inputbuf>
DECLARE @ret integer
EXEC @ret=hp_ZdvojeniKonstrukceATech 120234, 39008, 1
SELECT @ret </inputbuf>
</process>
<process id="process15bf6ce8c8" taskpriority="5" logused="0" waitresource="OBJECT: 7:2018106230:0 " waittime="6020" ownerId="394493191" transactionname="user_transaction" lasttranstarted="2019-06-05T12:06:39.750" XDES="0x16fb190ea0" lockMode="IX" schedulerid="1" kpid="3940" status="suspended" spid="83" sbid="0" ecid="0" priority="-5" trancount="2" lastbatchstarted="2019-06-05T12:06:39.750" lastbatchcompleted="2019-06-05T12:06:39.750" lastattention="1900-01-01T00:00:00.750" clientapp="Helios Orange" hostname="???" hostpid="14924" loginname="???" isolationlevel="read committed (2)" xactid="394493191" currentdb="7" currentdbname="XXXXXX" lockTimeout="4294967295" clientoption1="673384544" clientoption2="128568">
<executionStack>
<frame procname="XXXXXX.dbo.hp_ZdvojeniKonstrukceATech" line="73" stmtstart="6876" stmtend="7250" sqlhandle="0x03000700efbf9d27f8924d01e9a8000001000000000000000000000000000000000000000000000000000000">
UPDATE CZ SET IntPermanentniZmena=0
FROM TabDavka D
INNER JOIN TabCZmeny CZ ON (CZ.ID=D.ZmenaOd AND CZ.IntPermanentniZmena=1)
WHERE D.IDDilce=@IDKusovni </frame>
<frame procname="adhoc" line="2" stmtstart="44" stmtend="152" sqlhandle="0x0100070009fde604c031f1451a00000000000000000000000000000000000000000000000000000000000000">
EXEC @ret=hp_ZdvojeniKonstrukceATech 120073, 39047, 1 </frame>
</executionStack>
<inputbuf>
DECLARE @ret integer
EXEC @ret=hp_ZdvojeniKonstrukceATech 120073, 39047, 1
SELECT @ret </inputbuf>
</process>
</process-list>
<resource-list>
<objectlock lockPartition="0" objid="2018106230" subresource="FULL" dbid="7" objectname="XXXXXX.dbo.TabCzmeny" id="lock134d414780" mode="S" associatedObjectId="2018106230">
<owner-list>
<owner id="process15bf6ce8c8" mode="S"/>
<owner id="process15bf6ce8c8" mode="IX" requestType="convert"/>
</owner-list>
<waiter-list>
<waiter id="process17392264e8" mode="IX" requestType="convert"/>
</waiter-list>
</objectlock>
<objectlock lockPartition="0" objid="2018106230" subresource="FULL" dbid="7" objectname="XXXXXX.dbo.TabCzmeny" id="lock134d414780" mode="S" associatedObjectId="2018106230">
<owner-list>
<owner id="process17392264e8" mode="S"/>
<owner id="process17392264e8" mode="IX" requestType="convert"/>
</owner-list>
<waiter-list>
<waiter id="process15bf6ce8c8" mode="IX" requestType="convert"/>
</waiter-list>
</objectlock>
</resource-list>
</deadlock>
</deadlock-list>
Está no mesmo recurso (tabela) e está sendo causado pelo mesmo procedimento invocado de duas sessões.
O fluxo simplificado do procedimento é assim:
SELECT @Variable = Column FROM Table WITH (HOLDLOCK)
…
UPDATE Table SET...
Eu apreciaria muito qualquer conselho sobre como evitar esse cenário de impasse?
O problema é que o
HOLDLOCK
está criando o bloqueio "Compartilhado" (osMode: S
bloqueios) nesse recurso durante a transação. Isso não impede que outros processos (como o mesmo proc executado em outra sessão) coloquem seu próprio bloqueio "Compartilhado" no mesmo recurso. Mas então ambas as sessões chegam àUPDATE
instrução que está tentando converter (ou seja, atualizar) seu bloqueio para "Intent eXclusive" (osMode: IX
bloqueios), o que não pode ser feito devido à outra sessão ainda manter seu bloqueio "Compartilhado".Se o objetivo do
HOLDLOCK
é reservar a linha para essa sessão (para que outros não possam modificá-la), você precisa impedir que outras sessões possam colocar seu próprio bloqueio "Compartilhado" nesse mesmo recurso (ao mesmo tempo, é claro).Uma maneira de fazer isso é alterando o bloqueio
HOLDLOCK
paraUPDLOCK
(ou seja, "Atualizar"). Isso permite que outras transações tenham o mesmo bloqueio no mesmo recurso (comoHOLDLOCK
), mas uma dessas transações pode converter/atualizar para umXLOCK
que forçará todas as outras transações (ainda com seuUPDLOCK
) a esperar até que a transação termine (diferente deHOLDLOCK
) . Além disso, enquanto o bloqueio ainda é um bloqueio de "Atualização" (ou seja, antes de converter em um bloqueio "exclusivo"), outras transações/sessões podem ler esse recurso (também comoHOLDLOCK
).Se houver pelo menos uma linha para atualizar e o bloqueio for convertido em um bloqueio "exclusivo" e você tiver outros processos que precisam acessar essa tabela sem esperar que todas essas atualizações sejam concluídas, você terá algumas opções:
WITH (NOLOCK)
dica (ler modificado, mas ainda não confirmado, portanto, pode reverter a linha)WITH (READPAST)
dica (pule a linha inteiramente como se ela não existisse)SNAPSHOT ISOLATION
(ler a linha atual / não modificada)O teste a seguir deve ilustrar tudo isso:
Guia de consulta A
Cole o seguinte em uma guia de consulta no SSMS (ou seja, sessão) e execute apenas as instruções dentro da citação de bloco (por enquanto; ou seja
USE
,CREATE TABLE
, eINSERT
)Guia de consulta B
Cole o seguinte em outra guia de consulta no SSMS (ou seja, outra sessão), mas não a execute (ainda).
Guia de consulta C
Cole o seguinte em outra guia de consulta no SSMS (ou seja, outra sessão), mas não a execute (ainda).
Teste principal
sys.dm_tran_locks
). Se você tem um monte de coisas acontecendo no sistema, descomente aWHERE
cláusula e substituax
ey
pelos IDs de sessão (ou seja, SPIDs) para "Query Tab A" e "Query Tab B".SELECT ... FROM dbo.test ...
consultas. Até agora, apenas bloqueios de "Atualização" foram feitos.UPDATE
instrução, por alguns segundos:SELECT ... FROM dbo.test ...
consulta (ou sejaREADPAST
)NOLOCK
, ), mas a linha #3 mostrará o valor não confirmado.GO
existe, logo antes desta consulta, para que os outros possam retornar seus conjuntos de resultados enquanto esta fica bloqueado)