J'ai la procédure suivante (SQL Server 2008 R2):
create procedure usp_SaveCompanyUserData
@companyId bigint,
@userId bigint,
@dataTable tt_CoUserdata readonly
as
begin
set nocount, xact_abort on;
merge CompanyUser with (holdlock) as r
using (
select
@companyId as CompanyId,
@userId as UserId,
MyKey,
MyValue
from @dataTable) as newData
on r.CompanyId = newData.CompanyId
and r.UserId = newData.UserId
and r.MyKey = newData.MyKey
when not matched then
insert (CompanyId, UserId, MyKey, MyValue) values
(@companyId, @userId, newData.MyKey, newData.MyValue);
end;
CompanyId, UserId, MyKey forment la clé composite de la table cible. CompanyId est une clé étrangère vers une table parent. En outre, il existe un index non clusterisé CompanyId asc, UserId asc
.
Il est appelé à partir de nombreux threads différents, et je reçois constamment des blocages entre différents processus appelant cette même instruction. Ma compréhension était que le "avec (verrou)" était nécessaire pour éviter les erreurs d'insertion / mise à jour des conditions de concurrence.
Je suppose que deux threads différents verrouillent des lignes (ou des pages) dans des ordres différents lorsqu'ils valident les contraintes, et donc bloquent.
est-ce une supposition correcte?
Quelle est la meilleure façon de résoudre cette situation (c.-à-d. Pas de blocages, impact minimum sur les performances multi-thread)?
(Si vous affichez l'image dans un nouvel onglet, elle est lisible. Désolé pour la petite taille.)
- Il y a au plus 28 lignes dans le @datatable.
- J'ai retracé le code et je ne vois nulle part où nous commençons une transaction ici.
- La clé étrangère est configurée pour cascade uniquement lors de la suppression, et il n'y a pas eu de suppression de la table parent.
Il n'y aurait pas de problème si la variable de table ne contenait qu'une seule valeur. Avec plusieurs lignes, il existe une nouvelle possibilité de blocage. Supposons que deux processus simultanés (A et B) s'exécutent avec des variables de table contenant (1, 2) et (2, 1) pour la même société.
Le processus A lit la destination, ne trouve aucune ligne et insère la valeur «1». Il détient un verrou de ligne exclusif sur la valeur «1». Le processus B lit la destination, ne trouve aucune ligne et insère la valeur «2». Il détient un verrou de ligne exclusif sur la valeur «2».
Le processus A doit désormais traiter la ligne 2 et le processus B doit traiter la ligne 1. Aucun des deux processus ne peut progresser car il nécessite un verrou incompatible avec le verrou exclusif détenu par l'autre processus.
Pour éviter les blocages avec plusieurs lignes, les lignes doivent être traitées (et les tables accédées) dans le même ordre à chaque fois . La variable de table dans le plan d'exécution montré dans la question est un tas, donc les lignes n'ont pas d'ordre intrinsèque (elles sont très susceptibles d'être lues dans l'ordre d'insertion, bien que cela ne soit pas garanti):
L'absence d'un ordre de traitement des lignes cohérent conduit directement à l'opportunité de blocage. Une deuxième considération est que l'absence d'une garantie d'unicité clé signifie qu'une bobine de table est nécessaire pour fournir une protection Halloween correcte. Le spool est un spool enthousiaste, ce qui signifie que toutes les lignes sont écrites dans une table de travail tempdb avant d'être lues et relues pour l'opérateur d'insertion.
Redéfinir la
TYPE
variable de table pour inclure un clusterPRIMARY KEY
:Le plan d'exécution affiche désormais une analyse de l'index clusterisé et la garantie d'unicité signifie que l'optimiseur est en mesure de supprimer le spouleur de table en toute sécurité:
Dans les tests avec 5000 itérations de l'
MERGE
instruction sur 128 threads, aucun blocage n'a eu lieu avec la variable de table en cluster. Je dois souligner que ce n'est que sur la base de l'observation; la variable de table en cluster pourrait également ( techniquement ) produire ses lignes dans une variété d'ordres, mais les chances d'un ordre cohérent sont très considérablement améliorées. Le comportement observé devrait être retesté pour chaque nouvelle mise à jour cumulative, service pack ou nouvelle version de SQL Server, bien sûr.Si la définition de la variable de table ne peut pas être modifiée, il existe une autre alternative:
Cela permet également d'éliminer le spool (et la cohérence de l'ordre des lignes) au prix de l'introduction d'un tri explicite:
Ce plan n'a également produit aucun blocage en utilisant le même test. Script de reproduction ci-dessous:
la source
Je pense que SQL_Kiwi a fourni une très bonne analyse. Si vous devez résoudre le problème dans la base de données, vous devez suivre sa suggestion. Bien sûr, vous devez retester qu'il fonctionne toujours pour vous chaque fois que vous effectuez une mise à niveau, appliquez un Service Pack ou ajoutez / modifiez un index ou une vue indexée.
Il existe trois autres alternatives:
Vous pouvez sérialiser vos insertions afin qu'elles ne se heurtent pas: vous pouvez invoquer sp_getapplock au début de votre transaction et acquérir un verrou exclusif avant d'exécuter votre MERGE. Bien sûr, vous devez encore le tester.
Vous pouvez avoir un thread gérer toutes vos insertions, de sorte que votre serveur d'applications gère la concurrence.
Vous pouvez réessayer automatiquement après les blocages - cela peut être l'approche la plus lente si la simultanéité est élevée.
Quoi qu'il en soit, vous seul pouvez déterminer l'impact de votre solution sur les performances.
En règle générale, nous n'avons pas de blocages dans notre système, bien que nous ayons beaucoup de potentiel pour les avoir. En 2011, nous avons commis une erreur dans un déploiement et une demi-douzaine de blocages se sont produits en quelques heures, tous suivant le même scénario. J'ai corrigé cela rapidement et c'était tous les blocages de l'année.
Nous utilisons principalement l'approche 1 dans notre système. Cela fonctionne vraiment bien pour nous.
la source
Une autre approche possible - j'ai trouvé que Merge présente parfois des problèmes de verrouillage et de performances - cela peut valoir la peine de jouer avec l'option de requête Option (MaxDop x)
Dans un passé sombre et lointain, SQL Server avait une option de verrouillage de niveau d'insertion de ligne - mais cela semble être mort, mais un PK en cluster avec une identité devrait rendre les insertions propres.
la source