Estoy resolviendo un problema de bloqueo mientras noté que el comportamiento de bloqueo es diferente cuando uso el índice agrupado y no agrupado en el campo id. El problema de punto muerto parece resolverse si el índice agrupado o la clave primaria se aplican al campo id.
Tengo diferentes transacciones haciendo una o más actualizaciones a diferentes filas, por ejemplo, la transacción A solo actualizará la fila con ID = a, tx B solo tocará la fila con ID = b, etc.
Y tengo entendido que sin índice, la actualización adquirirá un bloqueo de actualización para todas las filas y se convertirá en un bloqueo exclusivo cuando sea necesario, lo que eventualmente conducirá a un punto muerto. Pero no entiendo por qué con el índice no agrupado, el punto muerto sigue ahí (aunque la tasa de aciertos parece haberse reducido)
Tabla de datos:
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
)
Rastro de punto muerto
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
También un hallazgo interesante y posible relacionado es que el índice agrupado y no agrupado parece tener diferentes comportamientos de bloqueo
Cuando se usa el índice agrupado, hay un bloqueo exclusivo en la clave, así como un bloqueo exclusivo en RID cuando se actualiza, lo que se espera; mientras que hay dos bloqueos exclusivos en dos RID diferentes si se utiliza un índice no agrupado, lo que me confunde.
Sería útil si alguien puede explicar por qué en esto también.
Prueba SQL:
use SQL2012_707688_webows;
begin transaction;
update [user] with (rowlock) set todoOrder='{1}' where id = 63501
exec sp_lock;
commit;
Con id como índice agrupado:
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
Con id como índice no agrupado
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
EDITAR1: Detalles del punto muerto sin ningún índice.
Digamos que tengo dos tx A y B, cada uno con dos declaraciones de actualización, una fila diferente, por supuesto,
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} y {4} tendrían una posibilidad de punto muerto, ya que
en {1}, se solicita el bloqueo U para la fila 63502 ya que necesita hacer un escaneo de la tabla, y el bloqueo X podría haberse mantenido en la fila 63501 ya que coincide con la condición
en {4}, se solicita el bloqueo U para la fila 63501 y el bloqueo X ya se mantiene para 63502
entonces tenemos txA contiene 63501 y espera 63502 mientras que txB contiene 63502 esperando 63501, que es un punto muerto
EDIT2: Resulta que un error de mi caso de prueba hace una situación diferente aquí Perdón por la confusión, pero el error hace una situación diferente, y parece causar el punto muerto eventualmente.
Como el análisis de Paul realmente me ayudó en este caso, lo aceptaré como respuesta.
Debido al error de mi caso de prueba, dos transacciones txA y txB pueden actualizar la misma fila, como se muestra a continuación:
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} y {3} tendrían una posibilidad de punto muerto cuando:
txA solicita bloqueo U en la tecla mientras mantiene el bloqueo X en RID (debido a la actualización de {1}) txB solicita bloqueo U en RID mientras mantiene el bloqueo U en la tecla
fuente
Respuestas:
La pregunta no es clara (por ejemplo, cuántas actualizaciones y qué
id
valores hay en cada transacción), pero surge un escenario de punto muerto obvio con múltiples actualizaciones de una sola fila dentro de una sola transacción, donde hay una superposición de[id]
valores, y los identificadores son actualizado en un[id]
orden diferente :Secuencia de bloqueo: T1 (u2), T2 (u1), T1 (u1) espera , T2 (u2) espera .
Esta secuencia de bloqueo podría evitarse actualizando estrictamente en orden de identificación dentro de cada transacción (adquiriendo bloqueos en el mismo orden en la misma ruta).
Con un índice agrupado único activado, se activa
id
un bloqueo exclusivo en la clave de agrupamiento para proteger las escrituras en los datos de la fila. SeRID
requiere un bloqueo exclusivo por separado para proteger la escritura en latext
columna LOB , que se almacena en una página de datos separada de forma predeterminada.Cuando la tabla es un montón con solo un índice no agrupado activado
id
, suceden dos cosas. Primero, unRID
bloqueo exclusivo se relaciona con los datos de la fila del montón, y el otro es el bloqueo de los datos LOB como antes. El segundo efecto es que se requiere un plan de ejecución más complejo.Con un índice agrupado y una actualización simple de predicado de igualdad de valor único, el procesador de consultas puede aplicar una optimización que realiza la actualización (lectura y escritura) en un solo operador, utilizando una única ruta:
La fila se ubica y actualiza en una sola operación de búsqueda, que requiere solo bloqueos exclusivos (no se necesitan bloqueos de actualización). Un ejemplo de secuencia de bloqueo usando su tabla de muestra:
Con solo un índice no agrupado, no se puede aplicar la misma optimización porque necesitamos leer de una estructura b-tree y escribir otra. El plan de múltiples rutas tiene fases separadas de lectura y escritura:
Esto adquiere bloqueos de actualización al leer, convirtiéndolos en bloqueos exclusivos si la fila califica. Ejemplo de secuencia de bloqueo con el esquema dado:
Tenga en cuenta que los datos de LOB se leen y escriben en el iterador Actualización de tabla. El plan más complejo y las múltiples rutas de lectura y escritura aumentan las posibilidades de un punto muerto.
Finalmente, no puedo evitar notar los tipos de datos utilizados en la definición de la tabla. No debe usar el
text
tipo de datos en desuso para un nuevo trabajo; la alternativa, si realmente necesita la capacidad de almacenar hasta 2 GB de datos en esta columna, esvarchar(max)
. Una diferencia importante entretext
yvarchar(max)
es que lostext
datos se almacenan fuera de la fila de forma predeterminada, mientras que sevarchar(max)
almacenan en fila de forma predeterminada.Utilice los tipos Unicode solo si necesita esa flexibilidad (por ejemplo, es difícil ver por qué una dirección IP necesitaría Unicode). Además, elija los límites de longitud apropiados para sus atributos: 255 en todas partes parece poco probable que sea correcto.
Lectura adicional:
Deadlock y livelock patrones comunes
Serie de solución de problemas de deadlock de Bart Duncan
Los bloqueos de rastreo se pueden realizar de varias maneras. SQL Server Express con servicios avanzados (solo 2014 y 2012 SP1 en adelante ) contiene la herramienta Profiler , que es una forma compatible de ver los detalles de la adquisición y liberación de bloqueos.
fuente