Pourquoi cela ne fonctionne-t-il pas ?
Je pense que le comportement par défaut de SQL Server est de libérer les verrous partagés dès qu'ils ne sont plus nécessaires. Votre sous-requête entraînera un verrou partagé (S) de courte durée sur la table, qui sera libéré dès que la sous-requête sera terminée.
À ce stade, rien n'empêche une transaction simultanée d'insérer la ligne dont vous venez de vérifier qu'elle n'était pas présente.
Quelle modification dois-je apporter pour qu'il n'y ait aucune chance d'exception due à la violation de la contrainte ?
Ajout du HOLDLOCK
indice à votre sous-requête demandera à SQL Server de conserver le verrou jusqu'à ce que la transaction soit terminée. (Dans votre cas, il s'agit d'une transaction implicite.) Le HOLDLOCK
indice est équivalent à SERIALIZABLE
indice, qui lui-même équivaut au niveau d'isolation de transaction sérialisable auquel vous faites référence dans votre liste "d'autres approches".
Le HOLDLOCK
un indice seul serait suffisant pour conserver le verrou S et empêcher une transaction concurrente d'insérer la ligne contre laquelle vous vous protégez. Cependant, vous trouverez probablement votre erreur de violation de clé unique remplacée par des blocages, se produisant à la même fréquence.
Si vous ne conservez qu'un verrou S sur la table, considérez une course entre deux tentatives simultanées d'insertion de la même ligne, en procédant en parallèle - les deux réussissent à acquérir un verrou S sur la table, mais aucun ne peut réussir à acquérir le Exclusif (X) verrou requis pour exécuter l'insertion.
Heureusement, il existe un autre type de verrou pour ce scénario précis, appelé le verrou de mise à jour (U). Le verrou U est identique à un verrou S avec la différence suivante :alors que plusieurs verrous S peuvent être détenus simultanément sur la même ressource, un seul verrou U peut être détenu à la fois. (En d'autres termes, alors que les verrous S sont compatibles les uns avec les autres (c'est-à-dire qu'ils peuvent coexister sans conflit), les verrous U ne sont pas compatibles les uns avec les autres, mais peuvent coexister avec les verrous S ; et plus loin dans le spectre, les verrous exclusifs (X) ne sont pas compatible avec les serrures S ou U)
Vous pouvez mettre à niveau le verrou S implicite sur votre sous-requête vers un verrou U en utilisant le UPDLOCK
indice.
Deux tentatives simultanées d'insertion de la même ligne dans la table seront désormais sérialisées à l'instruction de sélection initiale, car celle-ci acquiert (et détient) un verrou U, qui n'est pas compatible avec un autre verrou U de la tentative d'insertion simultanée.
Valeurs NULL
Un problème distinct peut survenir du fait que FieldC autorise les valeurs NULL.
Si ANSI_NULLS
est activé (par défaut) alors le contrôle d'égalité FieldC=NULL
renverrait false, même dans le cas où FieldC est NULL (vous devez utiliser le IS NULL
opérateur pour vérifier la valeur null lorsque ANSI_NULLS
est sur). Étant donné que FieldC est nullable, votre vérification des doublons ne fonctionnera pas lors de l'insertion d'une valeur NULL.
Pour traiter correctement les valeurs nulles, vous devrez modifier votre sous-requête EXISTS pour utiliser le IS NULL
opérateur plutôt que =
lorsqu'une valeur NULL est insérée. (Ou vous pouvez modifier le tableau pour interdire les valeurs NULL dans toutes les colonnes concernées.)
Références en ligne de la documentation SQL Server
- Conseils de verrouillage
- Matrice de compatibilité des verrous
- ANSI_NULLS