J'ai une situation où je reçois des blocages, et je pense que j'ai réduit les coupables, mais je ne sais pas trop ce que je peux faire pour y remédier.
Il s'agit d'un environnement de production exécutant SQL Server 2008 R2.
Pour vous donner une vue légèrement simplifiée de la situation:
J'ai 3 tableaux tels que définis ci-dessous:
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
table a une clé primaire composée définie comme member_id, activity_id
, car je n'ai besoin que de rechercher des données sur cette table de cette façon.
J'ai également un index non clusterisé sur follow
:
CREATE NONCLUSTERED INDEX [IX_follow_member_id_includes]
ON follow ( member_id ASC ) INCLUDE ( follower_id )
De plus, j'ai une vue liée au schéma network_activity
qui est définie comme suit:
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
Qui a également un index cluster unique:
CREATE UNIQUE CLUSTERED INDEX [IX_network_activity_unique_member_id_activity_id]
ON network_activity
(
member_id ASC,
activity_id ASC
)
Maintenant, j'ai deux procédures stockées bloquées. Ils passent par le processus suivant:
-- 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 (...)
Ces 2 procédures s'exécutent toutes les deux dans l'isolement READ COMMITTED. J'ai réussi à interroger la sortie des événements étendus 1222 et j'ai interprété ce qui suit en ce qui concerne les blocages:
SP1 attend un
RangeS-S
verrou de clé sur l'IX_follow_member_id_includes
index tandis que SP2 détient un verrou (X) en conflitSP2 attend un
S
verrouillage de mode activéPK_member_activity
tandis que SP1 détient un verrou (X) en conflit
Le blocage semble se produire sur la dernière ligne de chaque requête (les insertions). Ce qui n'est pas clair pour moi, c'est pourquoi SP1 veut un verrou sur l' IX_follow-member_id_includes
index. Le seul lien, pour moi, semble provenir de cette vue indexée, c'est pourquoi je l'ai inclus.
Quelle serait la meilleure façon pour moi d'empêcher ces blocages de se produire? Toute aide serait très appréciée. Je n'ai pas beaucoup d'expérience dans la résolution des problèmes de blocage.
Veuillez me faire savoir s'il y a plus d'informations que je peux fournir qui pourraient aider!
Merci d'avance.
Edit 1: Ajout de plus d'informations par demande.
Voici la sortie 1222 de ce blocage:
<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>
Dans ce cas,
associatedObjectId 72057594098679808 correspond à member_activity, PK_member_activity
associatedObjectId 72057594104905728 correspond à follow, IX_follow_member_id_includes
En outre, voici une image plus précise de ce que font SP1 et 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)
également 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)
Edit 2: Après avoir relu les commentaires, j'ai pensé ajouter quelques informations sur les colonnes qui sont également des clés étrangères ...
member_activity.member_id
est une clé étrangère vers unemember
tablemember_activity.activity_id
est une clé étrangère de laactivity
tablefollow.member_id
est une clé étrangère vers unemember
tablefollow.follower_id
est une clé étrangère vers unemember
table
Mise à jour 1:
J'ai apporté quelques changements qui, à mon avis, pourraient aider à éviter l'impasse, sans succès.
Les modifications que j'ai apportées étaient les suivantes:
-- 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)
;
et avec 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
Avec ces deux changements, je semble toujours avoir des blocages.
S'il y a autre chose que je peux fournir, faites-le moi savoir. Merci.
la source
SERIALIZABLE
(il y a un peu plus que cela, mais ce n'est pas une réponse :)Réponses:
Le conflit se résume à
network_activity
être une vue indexée qui doit être maintenue (en interne) dans les instructions DML. C'est probablement pourquoi SP1 souhaite un verrou sur l'IX_follow-member_id_includes
index car il est probablement utilisé par la vue (il semble être un index de couverture pour la vue).Deux options possibles:
Envisagez de supprimer l'index clusterisé sur la vue afin qu'il ne soit plus une vue indexée. L'avantage de l'avoir l'emporte-t-il sur le coût de maintenance? Le sélectionnez-vous assez fréquemment ou le gain de performances de l'indexation en vaut-il la peine? Si vous exécutez ces procs assez fréquemment, alors peut-être que le coût est supérieur à l'avantage?
Si l'avantage de l'indexation de la vue l'emporte sur le coût, envisagez d'isoler les opérations DML par rapport aux tables de base de cette vue. Cela peut être fait en utilisant des verrous d'application (voir sp_getapplock et sp_releaseapplock ). Les verrous d'application vous permettent de créer des verrous autour de concepts arbitraires. Ce qui signifie que vous pouvez définir le
@Resource
"network_activity" dans vos deux Proc stockés, ce qui les forcera à attendre leur tour. Chaque proc suivrait la même structure:Vous devez gérer les erreurs /
ROLLBACK
vous - même (comme indiqué dans la documentation MSDN liée), donc mettez-le dans l'habituelTRY...CATCH
. Mais cela vous permet de gérer la situation.Veuillez noter:
sp_getapplock
/sp_releaseapplock
devrait être utilisé avec parcimonie; Les verrous d'application peuvent certainement être très pratiques (comme dans des cas comme celui-ci), mais ils ne doivent être utilisés qu'en cas de nécessité absolue.la source
member_id
dans la@Resource
valeur. Cela ne semble pas s'appliquer à cette situation particulière, mais je l'ai vu utilisé comme ça et c'est assez pratique, en particulier dans un système multi-locataire où vous souhaitez limiter le processus à un seul thread sur une base par client, mais l'ont toujours être multithread à travers les clients.