Estou resolvendo um problema de deadlock enquanto observava que o comportamento do bloqueio é diferente quando uso o índice em cluster e não em cluster no campo id. O problema de deadlock parece ser resolvido se o índice confinado ou a chave primária for aplicada ao campo id.
Tenho transações diferentes fazendo uma ou mais atualizações em linhas diferentes, por exemplo, a transação A atualizará apenas a linha com ID = a, tx B só tocará a linha com ID = b etc.
E compreendi que, sem o índice, a atualização adquirirá o bloqueio de atualização para todas as linhas e ocultará o bloqueio exclusivo quando necessário, o que acabará por levar a um impasse. Mas não consigo descobrir por que, com o índice não agrupado, o impasse ainda está lá (embora a taxa de acertos pareça ter caído)
Tabela de dados:
CREATE TABLE [dbo].[user](
[id] [int] IDENTITY(1,1) NOT NULL,
[userName] [nvarchar](255) NULL,
[name] [nvarchar](255) NULL,
[phone] [nvarchar](255) NULL,
[password] [nvarchar](255) NULL,
[ip] [nvarchar](30) NULL,
[email] [nvarchar](255) NULL,
[pubDate] [datetime] NULL,
[todoOrder] [text] NULL
)
Rastreio de impasse
deadlock-list
deadlock victim=process4152ca8
process-list
process id=process4152ca8 taskpriority=0 logused=0 waitresource=RID: 5:1:388:29 waittime=3308 ownerId=252354 transactionname=user_transaction lasttranstarted=2014-04-11T00:15:30.947 XDES=0xb0bf180 lockMode=U schedulerid=3 kpid=11392 status=suspended spid=57 sbid=0 ecid=0 priority=0 trancount=2 lastbatchstarted=2014-04-11T00:15:30.953 lastbatchcompleted=2014-04-11T00:15:30.950 lastattention=1900-01-01T00:00:00.950 clientapp=.Net SqlClient Data Provider hostname=BOOD-PC hostpid=9272 loginname=getodo_sql isolationlevel=read committed (2) xactid=252354 currentdb=5 lockTimeout=4294967295 clientoption1=671088672 clientoption2=128056
executionStack
frame procname=adhoc line=1 stmtstart=62 sqlhandle=0x0200000062f45209ccf17a0e76c2389eb409d7d970b0f89e00000000000000000000000000000000
update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@owner
frame procname=unknown line=1 sqlhandle=0x00000000000000000000000000000000000000000000000000000000000000000000000000000000
unknown
inputbuf
(@para0 nvarchar(2)<c/>@owner int)update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@owner
process id=process4153468 taskpriority=0 logused=4652 waitresource=KEY: 5:72057594042187776 (3fc56173665b) waittime=3303 ownerId=252344 transactionname=user_transaction lasttranstarted=2014-04-11T00:15:30.920 XDES=0x4184b78 lockMode=U schedulerid=3 kpid=7272 status=suspended spid=58 sbid=0 ecid=0 priority=0 trancount=2 lastbatchstarted=2014-04-11T00:15:30.960 lastbatchcompleted=2014-04-11T00:15:30.960 lastattention=1900-01-01T00:00:00.960 clientapp=.Net SqlClient Data Provider hostname=BOOD-PC hostpid=9272 loginname=getodo_sql isolationlevel=read committed (2) xactid=252344 currentdb=5 lockTimeout=4294967295 clientoption1=671088672 clientoption2=128056
executionStack
frame procname=adhoc line=1 stmtstart=60 sqlhandle=0x02000000d4616f250747930a4cd34716b610a8113cb92fbc00000000000000000000000000000000
update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@uid
frame procname=unknown line=1 sqlhandle=0x00000000000000000000000000000000000000000000000000000000000000000000000000000000
unknown
inputbuf
(@para0 nvarchar(61)<c/>@uid int)update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@uid
resource-list
ridlock fileid=1 pageid=388 dbid=5 objectname=SQL2012_707688_webows.dbo.user id=lock3f7af780 mode=X associatedObjectId=72057594042122240
owner-list
owner id=process4153468 mode=X
waiter-list
waiter id=process4152ca8 mode=U requestType=wait
keylock hobtid=72057594042187776 dbid=5 objectname=SQL2012_707688_webows.dbo.user indexname=10 id=lock3f7ad700 mode=U associatedObjectId=72057594042187776
owner-list
owner id=process4152ca8 mode=U
waiter-list
waiter id=process4153468 mode=U requestType=wait
Também uma descoberta interessante e possível é que o índice clusterizado e não clusterizado parece ter comportamentos de bloqueio diferentes
Ao usar o índice em cluster, há um bloqueio exclusivo na chave, bem como um bloqueio exclusivo no RID ao atualizar, o que é esperado; enquanto houver dois bloqueios exclusivos em dois RID diferentes se for usado um índice não clusterizado, o que me confunde.
Seria útil se alguém puder explicar o porquê disso também.
Teste de SQL:
use SQL2012_707688_webows;
begin transaction;
update [user] with (rowlock) set todoOrder='{1}' where id = 63501
exec sp_lock;
commit;
Com id como índice clusterizado:
spid dbid ObjId IndId Type Resource Mode Status
53 5 917578307 1 KEY (b1a92fe5eed4) X GRANT
53 5 917578307 1 PAG 1:879 IX GRANT
53 5 917578307 1 PAG 1:1928 IX GRANT
53 5 917578307 1 RID 1:879:7 X GRANT
Com id como índice não clusterizado
spid dbid ObjId IndId Type Resource Mode Status
53 5 917578307 0 PAG 1:879 IX GRANT
53 5 917578307 0 PAG 1:1928 IX GRANT
53 5 917578307 0 RID 1:879:7 X GRANT
53 5 917578307 0 RID 1:1928:18 X GRANT
EDIT1: Detalhes do impasse sem nenhum índice
Digamos que eu tenha dois tx A e B, cada um com duas instruções de atualização, linha diferente do curso
tx A
update [user] with (rowlock) set todoOrder='{1}' where id = 63501
update [user] with (rowlock) set todoOrder='{2}' where id = 63501
tx B
update [user] with (rowlock) set todoOrder='{3}' where id = 63502
update [user] with (rowlock) set todoOrder='{4}' where id = 63502
{1} e {4} teriam uma chance de impasse, uma vez que
em {1}, o bloqueio U é solicitado para a linha 63502, pois ele precisa fazer uma varredura de tabela, e o bloqueio X poderia ter sido retido na linha 63501, pois corresponde à condição
em {4}, o bloqueio U é solicitado para a linha 63501 e o bloqueio X já é válido para 63502
portanto, txA retém 63501 e aguarda 63502 enquanto txB retém 63502 aguardando 63501, que é um impasse
EDIT2: Acontece que um bug do meu caso de teste faz uma situação diferente aqui Desculpe por confusão, mas o bug faz uma situação diferente e parece causar o impasse eventualmente.
Como a análise de Paul realmente me ajudou nesse caso, eu aceitarei isso como resposta.
Devido ao erro do meu caso de teste, duas transações txA e txB podem atualizar a mesma linha, como abaixo:
tx A
update [user] with (rowlock) set todoOrder='{1}' where id = 63501
update [user] with (rowlock) set todoOrder='{2}' where id = 63501
tx B
update [user] with (rowlock) set todoOrder='{3}' where id = 63501
{2} e {3} teriam uma chance de conflito quando:
txA solicita bloqueio U na chave enquanto mantém o bloqueio X no RID (devido à atualização de {1}) txB solicita bloqueio U no RID enquanto mantém o bloqueio U na chave
fonte
Respostas:
A questão não é clara (por exemplo, quantas atualizações e quais
id
valores estão em cada transação), mas um cenário óbvio de conflito surge com várias atualizações de linha única em uma única transação, onde há uma sobreposição de[id]
valores e os IDs são atualizado em uma[id]
ordem diferente :Impasse sequência: T1 (u2), T2 (u1), T1 (u1) de espera , T2 (u2) de espera .
Essa sequência de conflito pode ser evitada atualizando estritamente na ordem de identificação em cada transação (adquirindo bloqueios na mesma ordem no mesmo caminho).
Com um índice clusterizado exclusivo ativado
id
, um bloqueio exclusivo é executado na chave de cluster para proteger gravações nos dados em linha. UmRID
bloqueio exclusivo separado é necessário para proteger a gravação natext
coluna LOB , que é armazenada em uma página de dados separada por padrão.Quando a tabela é um heap com apenas um índice não clusterizado
id
, duas coisas acontecem. Primeiro, umRID
bloqueio exclusivo refere-se aos dados em heap em linha e o outro é o bloqueio nos dados do LOB como antes. O segundo efeito é que é necessário um plano de execução mais complexo.Com um índice em cluster e uma atualização simples de predicado de igualdade de valor único, o processador de consultas pode aplicar uma otimização que executa a atualização (leitura e gravação) em um único operador, usando um único caminho:
A linha está localizada e atualizada em uma única operação de busca, exigindo apenas bloqueios exclusivos (nenhum bloqueio de atualização é necessário). Um exemplo de sequência de bloqueio usando sua tabela de amostra:
Com apenas um índice não clusterizado, a mesma otimização não pode ser aplicada porque precisamos ler de uma estrutura de árvore b e escrever outra. O plano de caminhos múltiplos possui fases separadas de leitura e gravação:
Isso adquire bloqueios de atualização ao ler, convertendo em bloqueios exclusivos, se a linha for qualificada. Exemplo de sequência de bloqueio com o esquema fornecido:
Observe que os dados LOB são lidos e gravados no iterador de atualização de tabela. O plano mais complexo e vários caminhos de leitura e gravação aumentam as chances de um conflito.
Por fim, não posso deixar de notar os tipos de dados usados na definição da tabela. Você não deve usar o
text
tipo de dados descontinuado para novos trabalhos; a alternativa, se você realmente precisa armazenar até 2 GB de dados nesta coluna, évarchar(max)
. Uma diferença importante entretext
evarchar(max)
é que ostext
dados são armazenados fora da linha por padrão, enquanto osvarchar(max)
armazenam na linha por padrão.Use tipos Unicode apenas se você precisar dessa flexibilidade (por exemplo, é difícil ver por que um endereço IP precisaria de Unicode). Além disso, escolha limites de comprimento apropriados para seus atributos - 255 em qualquer lugar parece improvável que esteja correto.
Leitura adicional:
Padrões comuns de impasse e livelock
Série de solução de problemas de impasse de Bart Duncan
O bloqueio de rastreamento pode ser feito de várias maneiras. O SQL Server Express com Serviços Avançados ( somente 2014 e 2012 SP1 em diante ) contém a ferramenta Profiler , que é uma maneira suportada de exibir os detalhes da aquisição e liberação de bloqueios.
fonte