Tengo una situación en la que obtengo puntos muertos y creo que he reducido los culpables, pero no estoy muy seguro de qué puedo hacer para solucionarlo.
Esto está en un entorno de producción que ejecuta SQL Server 2008 R2.
Para darle una visión ligeramente simplificada de la situación:
Tengo 3 tablas como se define a continuación:
TABLE activity (
id, -- PK
...
)
TABLE member_activity (
member_id, -- PK col 1
activity_id, -- PK col 2
...
)
TABLE follow (
id, -- PK
follower_id,
member_id,
...
)
La member_activity
tabla tiene una clave primaria compuesta definida comomember_id, activity_id
, porque solo necesito buscar datos en esa tabla de esa manera.
También tengo un índice no agrupado en follow
:
CREATE NONCLUSTERED INDEX [IX_follow_member_id_includes]
ON follow ( member_id ASC ) INCLUDE ( follower_id )
Además, tengo una vista vinculada al esquema network_activity
que se define de la siguiente manera:
CREATE VIEW network_activity
WITH SCHEMABINDING
AS
SELECT
follow.follower_id as member_id,
member_activity.activity_id as activity_id,
COUNT_BIG(*) AS cb
FROM member_activity
INNER JOIN follow ON follow.member_id = member_activity.member_id
INNER JOIN activity ON activity.id = member_activity.activity_id
GROUP BY follow.follower_id, member_activity.activity_id
Que también tiene un índice agrupado único:
CREATE UNIQUE CLUSTERED INDEX [IX_network_activity_unique_member_id_activity_id]
ON network_activity
(
member_id ASC,
activity_id ASC
)
Ahora, tengo dos procedimientos almacenados bloqueados. Pasan por el siguiente proceso:
-- SP1: insert activity
-----------------------
INSERT INTO activity (...)
SELECT ... FROM member_activity WHERE member_id = @a AND activity_id = @b
INSERT INTO member_activity (...)
-- SP2: insert follow
---------------------
SELECT follow WHERE member_id = @x AND follower_id = @y
INSERT INTO follow (...)
Estos 2 procedimientos se ejecutan en aislamiento READ COMMITTED. Me las arreglé para consultar la salida de 1222 eventos extendidos, y he interpretado lo siguiente con respecto a los puntos muertos:
SP1 está esperando un
RangeS-S
bloqueo de teclas en elIX_follow_member_id_includes
índice mientras que SP2 tiene un conflicto (X)SP2 espera un
S
bloqueo de modo activadoPK_member_activity
mientras SP1 mantiene un bloqueo conflictivo (X)
El punto muerto parece estar sucediendo en la última línea de cada consulta (las inserciones). Lo que no está claro para mí es por qué SP1 quiere un bloqueo en elIX_follow-member_id_includes
índice. El único enlace, para mí, parece ser de esta vista indexada y es por eso que lo he incluido.
¿Cuál sería la mejor manera para mí de evitar que ocurran estos puntos muertos? Cualquier ayuda sería muy apreciada. No tengo mucha experiencia en resolver problemas de punto muerto.
¡Avíseme si hay más información que pueda proporcionar que pueda ayudar!
Gracias por adelantado.
Edición 1: Agregar más información por solicitud.
Aquí está la salida 1222 de este punto muerto:
<deadlock>
<victim-list>
<victimProcess id="process4c6672748" />
</victim-list>
<process-list>
<process id="process4c6672748" taskpriority="0" logused="332" waitresource="KEY: 8:72057594104905728 (25014f77eaba)" waittime="581" ownerId="474698706" transactionname="INSERT" lasttranstarted="2014-07-03T17:03:12.287" XDES="0x298487970" lockMode="RangeS-S" schedulerid="1" kpid="972" status="suspended" spid="79" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2014-07-03T17:03:12.283" lastbatchcompleted="2014-07-03T17:03:12.283" lastattention="2014-07-03T10:25:00.283" clientapp=".Net SqlClient Data Provider" hostname="WIN08CLYDESDALE" hostpid="4596" loginname="TechPro" isolationlevel="read committed (2)" xactid="474698706" currentdb="8" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="" line="7" stmtstart="1194" stmtend="1434" sqlhandle="0x02000000a26bb72a2b220406876cad09c22242e5265c82e6" />
<frame procname="" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000" />
</executionStack>
<inputbuf> <!-- SP 1 --> </inputbuf>
</process>
<process id="process6cddc5b88" taskpriority="0" logused="456" waitresource="KEY: 8:72057594098679808 (89013169fc76)" waittime="567" ownerId="474698698" transactionname="INSERT" lasttranstarted="2014-07-03T17:03:12.283" XDES="0x30c459970" lockMode="S" schedulerid="4" kpid="4204" status="suspended" spid="70" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2014-07-03T17:03:12.283" lastbatchcompleted="2014-07-03T17:03:12.283" lastattention="2014-07-03T15:04:55.870" clientapp=".Net SqlClient Data Provider" hostname="WIN08CLYDESDALE" hostpid="4596" loginname="TechPro" isolationlevel="read committed (2)" xactid="474698698" currentdb="8" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056">
<executionStack>
<frame procname="" line="18" stmtstart="942" stmtend="1250" sqlhandle="0x03000800ca458d315ee9130100a300000100000000000000" />
</executionStack>
<inputbuf> <!-- SP 2 --> </inputbuf>
</process>
</process-list>
<resource-list>
<keylock hobtid="72057594104905728" dbid="8" objectname="" indexname="" id="lock33299fc00" mode="X" associatedObjectId="72057594104905728">
<owner-list>
<owner id="process6cddc5b88" mode="X" />
</owner-list>
<waiter-list>
<waiter id="process4c6672748" mode="RangeS-S" requestType="wait" />
</waiter-list>
</keylock>
<keylock hobtid="72057594098679808" dbid="8" objectname="" indexname="" id="lockb7e2ba80" mode="X" associatedObjectId="72057594098679808">
<owner-list>
<owner id="process4c6672748" mode="X" />
</owner-list>
<waiter-list>
<waiter id="process6cddc5b88" mode="S" requestType="wait" />
</waiter-list>
</keylock>
</resource-list>
</deadlock>
En este caso,
AssociatedObjectId 72057594098679808 corresponde a member_activity, PK_member_activity
AssociatedObjectId 72057594104905728 corresponde a follow, IX_follow_member_id_includes
Además, aquí hay una imagen más precisa de lo que están haciendo SP1 y SP2
-- SP1: insert activity
-----------------------
DECLARE @activityId INT
INSERT INTO activity (field1, field2)
VALUES (@field1, @field2)
SET @activityId = SCOPE_IDENTITY();
IF NOT EXISTS(
SELECT TOP 1 member_id
FROM member_activity
WHERE member_id = @m1 AND activity_id = @activityId
)
INSERT INTO member_activity (member_id, activity_id, field1)
VALUES (@m1, @activityId, @field1)
IF NOT EXISTS(
SELECT TOP 1 member_id
FROM member_activity
WHERE member_id = @m2 AND activity_id = @activityId
)
INSERT INTO member_activity (member_id, activity_id, field1)
VALUES (@m2, @activityId, @field1)
también SP2:
-- SP2: insert follow
---------------------
IF NOT EXISTS(
SELECT TOP 1 1
FROM follow
WHERE member_id = @memberId AND follower_id = @followerId
)
INSERT INTO follow (member_id, follower_id)
VALUES (@memberId, @followerId)
Edición 2: después de releer los comentarios, pensé que también agregaría información sobre qué columnas son claves foráneas ...
member_activity.member_id
es una clave foránea para unmember
mesamember_activity.activity_id
es una clave foránea paraactivity
mesafollow.member_id
es una clave foránea para unmember
mesafollow.follower_id
es una clave foránea para unamember
mesa
Actualización 1:
Hice un par de cambios que pensé que podrían ayudar a evitar el punto muerto, sin suerte.
Los cambios que hice fueron los siguientes:
-- SP1: insert activity
-----------------------
DECLARE @activityId INT
INSERT INTO activity (field1, field2)
VALUES (@field1, @field2)
SET @activityId = SCOPE_IDENTITY();
MERGE member_activity WITH ( HOLDLOCK ) as target
USING (SELECT @m1 as member_id, @activityId as activity_id, @field1 as field1) as source
ON target.member_id = source.member_id
AND target.activity_id = source.activity_id
WHEN NOT MATCHED THEN
INSERT (member_id, activity_id, field1)
VALUES (source.member_id, source.activity_id, source.field1)
;
MERGE member_activity WITH ( HOLDLOCK ) as target
USING (SELECT @m2 as member_id, @activityId as activity_id, @field1 as field1) as source
ON target.member_id = source.member_id
AND target.activity_id = source.activity_id
WHEN NOT MATCHED THEN
INSERT (member_id, activity_id, field1)
VALUES (source.member_id, source.activity_id, source.field1)
;
y con SP2:
-- SP2: insert follow
---------------------
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION
IF NOT EXISTS(
SELECT TOP 1 1
FROM follow WITH ( UPDLOCK )
WHERE member_id = @memberId AND follower_id = @followerId
)
INSERT INTO follow (member_id, follower_id)
VALUES (@memberId, @followerId)
COMMIT
Con estos dos cambios, todavía parece que estoy llegando a un punto muerto.
Si hay algo más que pueda proporcionar, hágamelo saber. Gracias.
SERIALIZABLE
(hay un poco más que eso, pero este es un comentario, no una respuesta :)