Étant donné deux tableaux
Parent
KeyID GroupID Name Active
Enfant
KeyID ParentID Name
Child.ParentID
Est transféré vers Parent.KeyID
Nous insérons à la fois Parent
et Child
dans une seule transaction.
Si une ligne Parent
différente est mise à jour (par exemple. Active
1 -> 0) alors que la transaction est active, la Child
INSERT
échoue avec:
Transaction d'isolement d'instantané abandonnée en raison d'un conflit de mise à jour. Vous ne pouvez pas utiliser l'isolement d'instantané pour accéder à la table "dbo.Child" directement ou indirectement dans la base de données "Test" pour mettre à jour, supprimer ou insérer la ligne qui a été modifiée ou supprimée par une autre transaction. Relancez la transaction ou modifiez le niveau d'isolement pour l'instruction update/delete.
D'après ce que je peux dire de pourquoi est-ce que j'obtiens "transaction d'isolement d'instantané abandonnée en raison d'un conflit de mise à jour"? cela est probablement dû à une analyse complète pour vérifier la clé étrangère.
En effet, la suppression de la clé étrangère permet au Child
INSERT
de se terminer comme prévu.
Cela dit, aucune quantité d'index non cluster sur la clé étrangère sur la table Child
ne semble aider à résoudre ce problème, donc je ne sais pas quoi faire.
Nous avons activé RCSI pour cette base de données et la transaction s'exécute en mode d'isolement d'instantané.
Détails supplémentaires
J'ai découvert que ce problème se manifeste lorsque l'insertion dans Child est supérieure à un nombre donné de lignes. À ce stade, l'optimiseur de requête passe d'une Nested Loops (Left Semi Join)
à une Merge Join (Left Semi Join)
.
Toutes mes excuses pour ne pas avoir inclus le fait que plusieurs enregistrements enfants sont insérés pour un seul enregistrement parent.
Encart de travail (20 enregistrements enfants):
Insertion défaillante (50 enregistrements enfants):
Insérer sproc est à peu près ceci:
CREATE PROCEDURE dbo.[usp_InsertRecords] (
@journal dbo.ParentType READONLY,
@journalItems dbo.ChildType READONLY,
@tenantId INT
) AS
BEGIN
INSERT INTO dbo.Parent(GroupID, Name, Active, TenantId)
SELECT GroupID, Name, Active, @tenantId FROM @journal
DECLARE @JournalId INT = convert(int,scope_identity());
INSERT INTO dbo.Child(ParentID, Name, TenantId)
SELECT @JournalId, Name, @tenantId
FROM @journalItems j2
END
GO
Et une mise à jour simultanée serait quelque chose comme:
UPDATE dbo.Parent Set Active = 0 WHERE KeyID = 1234 -- row not being inserted
Ajoutez un indice OPTION (LOOP JOIN)
à l'instruction INSERT
.
Vous pouvez également utiliser un guide de plan (ou un magasin de requêtes) pour forcer la forme de plan de semi-jointure des boucles imbriquées.
Vous constaterez peut-être que OPTION (FAST 1)
fonctionne également.
Le but est d'éviter une semi-jointure de fusion, où de nombreuses (potentiellement toutes) des lignes des tables référencées sont touchées par la transaction en cours. Si une ligne parent avec une modification (y compris la création) est rencontrée, ne erreur de conflit de mise à jour est déclenchée .