Boas razões para usar SELECT… WITH XLOCK?

11

Estou enfrentando alguns deadlocks recorrentes, um dos quais é um Keylock e contém uma consulta SELECT com dica XLOCK que se torna a vítima do deadlock. A outra instrução é um INSERT em uma das tabelas que faz parte da exibição da primeira consulta.

Visão:

create view dbo.viewE
 as
    select * from dbo.E  
    where myValue > 13000 

Selecione Consulta:

select * from dbo.viewE with (XLOCK) where A > GETUTCDATE() 

Instrução INSERT:

INSERT INTO [dbo].[E] (myValue,A) VALUES (10,GetDate())

A tabela subjacente dbo.E está mantendo cerca de 3 milhões de linhas em cerca de 20 colunas, algumas delas são ntext.

Retirando as consultas e simulando-as manualmente com duas transações, o comportamento é reproduzível. O comportamento muda se o XLOCK for removido da seleção.

Gráfico de impasse:

<deadlock-list>
 <deadlock victim="process222222221">
  <process-list>
   <process id="process222222221" taskpriority="0" logused="0" waitresource="KEY: 5:72057604035644444 (ccdf51accc0c)" waittime="2522" ownerId="27202256401" transactionname="SELECT" lasttranstarted="2015-09-14T16:32:36.160" XDES="0x2f1ec5ca0" lockMode="RangeX-X" schedulerid="15" kpid="12936" status="suspended" spid="359" sbid="0" ecid="0" priority="0" trancount="0" lastbatchstarted="2015-09-14T16:32:36.160" lastbatchcompleted="2015-09-14T16:32:36.160" clientapp="x" hostname="x" hostpid="14536" loginname="x" isolationlevel="serializable (4)" xactid="27202256401" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
     <frame procname="adhoc" line="1" stmtstart="48" sqlhandle="0x02000000611e4523142b2318c47c87313a9b2ba587ff3130">
        SELECT * FROM viewE WITH (XLOCK) WHERE A &lt; GetUtcDate()      </frame>
     <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown     </frame>
    </executionStack>
    <inputbuf>
(@UICulture nvarchar(5))SELECT * FROM viewE WITH (XLOCK) WHERE A &lt; GetUtcDate()    </inputbuf>
   </process>
   <process id="process6022222" taskpriority="0" logused="161152" waitresource="KEY: 5:72057604035644444 (cd874c2ba438)" waittime="1370" ownerId="27202248438" transactionguid="0x8de5ccd6eeef67469c6234af59e44ca5" transactionname="DTCXact" lasttranstarted="2015-09-14T16:32:34.767" XDES="0x4aa0bf950" lockMode="RangeI-N" schedulerid="14" kpid="6636" status="suspended" spid="329" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2015-09-14T16:32:37.300" lastbatchcompleted="2015-09-14T16:32:37.300" clientapp="x" hostname="x" hostpid="14536" loginname="x" isolationlevel="read uncommitted (1)" xactid="27202248438" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
    <executionStack>
     <frame procname="adhoc" line="1" stmtstart="936" sqlhandle="0x020000004853462f09790a4ddedc0d574c2afa539aef1c0e">
     INSERT INTO [E] ([a], [b], [c],...) VALUES (@aDate, @bDate, @c, ...)
     </frame>
     <frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown     </frame>
    </executionStack>
    <inputbuf>INSERT INTO [E] ([a], [b], [c],...) VALUES (@aDate, @bDate, @c, ...)
    </inputbuf>
   </process>
  </process-list>
  <resource-list>
   <keylock hobtid="72057604035644444" dbid="5" objectname="db.dbo.E" indexname="IX_index1" id="lock258b6dc80" mode="X" associatedObjectId="72057604035644444">
    <owner-list>
     <owner id="process6022222" mode="X"/>
    </owner-list>
    <waiter-list>
     <waiter id="process222222221" mode="RangeX-X" requestType="wait"/>
    </waiter-list>
   </keylock>
   <keylock hobtid="72057604035644444" dbid="5" objectname="db.dbo.E" indexname="IX_index1" id="lock7b145c400" mode="RangeX-X" associatedObjectId="72057604035644444">
    <owner-list>
     <owner id="process222222221" mode="RangeX-X"/>
    </owner-list>
    <waiter-list>
     <waiter id="process6022222" mode="RangeI-N" requestType="wait"/>
    </waiter-list>
   </keylock>
  </resource-list>
 </deadlock>
</deadlock-list>

Pelo que entendi, estou analisando um impasse KEYLOCK causado basicamente por uma consulta de índice descoberta que usa um índice não clusterizado e um cluster para coletar os valores necessários, certo?

Minhas perguntas:

  1. Não consigo criar um índice de cobertura devido às colunas NTEXT necessárias envolvidas. Reduzir drasticamente o número de linhas ajudará aqui?
  2. Existe alguma boa razão para eu simplesmente não saber que o SELECT é executado com o XLOCK? O impasse também aconteceria sem o XLOCK?
Magier
fonte

Respostas:

15

Pelo que entendi, estou analisando um impasse KEYLOCK causado basicamente por uma consulta de índice descoberta que usa um índice não clusterizado e um cluster para coletar os valores necessários, certo?

Essencialmente sim. A operação de leitura (seleção) acessa primeiro o índice não clusterizado, depois o índice em cluster (pesquisa). A operação de gravação (inserção) acessa o índice em cluster primeiro, depois o índice não clusterizado. O acesso aos mesmos recursos em uma ordem diferente, mantendo bloqueios incompatíveis, pode levar a um conflito.

Reduzir drasticamente o número de linhas ajudará aqui?

Pode ser porque menos recursos estão bloqueados e a operação tenderá a ser concluída mais rapidamente. Se ajudar, pode reduzir os impasses, mas provavelmente não os eliminará (mas continue lendo).

Existe alguma boa razão para eu simplesmente não saber que o SELECT é executado com o XLOCK?

Na verdade não. Dicas de bloqueio como essa são frequentemente introduzidas por pessoas sem uma compreensão completa de como funcionam o isolamento, o bloqueio e os impasses, numa tentativa desesperada de reduzir ou eliminar um problema.

O impasse também aconteceria sem o XLOCK?

Não , se a seleção realmente for executada com isolamento não confirmado de leitura, porque bloqueios incompatíveis não serão executados (e mantidos) em uma ordem diferente.

Sim , se um nível de isolamento de bloqueio for usado e bloqueios incompatíveis forem obtidos e mantidos em uma ordem inconsistente, por exemplo, compartilhados (S) no não clusterizado e, em seguida, S no cluster ao ler. A probabilidade de um conflito nesse cenário depende de quantos bloqueios são executados e por quanto tempo eles são retidos.

Adendo

O que realmente se destaca (na revisão) é que a transação de seleção está sendo executada sob isolamento serializável . Isso pode estar sendo definido pela sua estrutura ou devido ao uso do DTC (Coordenador de Transações Distribuídas) - consulte transactionname = "DTCXact" no gráfico de deadlock. Você deve examinar as razões para isso e alterá-lo, se possível.

Sem essa escalação para serializável, as chances são muito boas de que esse conflito não ocorra, supondo que a XLOCKdica seja removida. Dito isto, você estaria lendo sob isolamento não confirmado de leitura , que vem com muito poucas garantias de consistência.

Se o seu aplicativo e o código do SQL Server puderem tolerar a leitura de versões de linhas, a alteração para ler RCSI (commit snapshot isolamento ) ou SI ( snapshot isolamento ) para as leituras também evitaria o impasse ( XLOCKremovido!), Ao mesmo tempo em que apresenta um apontamento consistente visualização em tempo real dos dados confirmados. Isso também pressupõe que você pode evitar o isolamento serializável, é claro.

Por fim, a XLOCKdica é contraproducente, mas você realmente precisa examinar o motivo do uso do nível de isolamento serializável. Isso trancount = 2também é interessante - talvez você esteja aninhando transações sem intenção aqui. Algo mais para verificar.

Paul White 9
fonte
2
  1. Reduzir drasticamente o número de linhas reduzirá a probabilidade de obter o impasse, mas não desaparecerá completamente.

Em termos simples, a seleção usa primeiro o índice para determinar as linhas a serem selecionadas e, em seguida, busca as linhas, enquanto a inserção está inserindo uma linha e, em seguida, tenta atualizar o índice (XLOCKED).

  1. Os desenvolvedores de aplicativos tendem a usar o XLOCK se, na mesma transação, quiserem fazer uma atualização posterior nos dados. Isso garante que ninguém possa atualizar os dados sob eles. Eu investigaria o que o aplicativo está fazendo para ver se o XLOCK é necessário.

Dito isto, remover o XLOCK provavelmente não resolverá o problema. O SELECT ainda executará um bloqueio compartilhado no índice e o INSERT desejará que um XLOCK o atualize. Um bloqueio compartilhado e um XLOCK não podem existir juntos no objeto, portanto você ainda terá um impasse. IX_Index1 deve ser MyValue ou A ou ambos.

Esse tipo de conflito geralmente ocorre devido a índices mal projetados e / ou muitos índices. Ou código mal escrito. Sua melhor opção é verificar se há alguma maneira de selecionar a reescrita para usar outro índice.

Leo Miller
fonte