web-dev-qa-db-fra.com

Pourquoi ai-je un problème d'isolement d'instantané sur INSERT?

É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 ChildINSERT é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 ChildINSERT 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): Working insert

Insertion défaillante (50 enregistrements enfants): Failing insert

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
11
joshschreuder

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 .

6
Paul White 9