web-dev-qa-db-fra.com

Comment éviter une erreur de "clé en double"?

Cette déclaration:

INSERT INTO deleteme
SELECT #t.id, 'test' FROM #t
LEFT JOIN  deleteme  ON deleteme.id = #t.id
WHERE deleteme.id IS NULL;

... échouera avec une violation de clé primaire dans le scénario simultané (c'est-à-dire lors de l'insertion de la même clé qui est absente dans deleteme de plusieurs threads en même temps) avec le niveau d'isolement de transaction par défaut de READ COMMITTED:

Erreur: violation de la contrainte PRIMARY KEY 'PK_DeleteMe'. Impossible d'insérer une clé en double dans l'objet 'dbo.deleteme'.

Quelle est la meilleure façon d'empêcher cela?

Le tableau ressemble à ceci:

CREATE TABLE [dbo].[DeleteMe](
    [id] [uniqueidentifier] NOT NULL,
    [Value] [varchar](max) NULL,
CONSTRAINT [PK_DeleteMe] PRIMARY KEY ([id] ASC));

Mise à jour

Des commentaires:

Pourquoi avez-vous plusieurs sessions, qui peuvent évidemment tirer le même identifiant de quelque part, en utilisant la même table permanente sans avoir une sorte de clé de session pour les distinguer? Même sans erreur en double, comment allez-vous savoir quelles lignes appartiennent à quelle session?

Il s'agit d'une procédure stockée appelée par un service externe qui remplit les données de cette table. Le service génère l'ID d'enregistrement et il ne garantit pas qu'il n'enverra pas les mêmes données deux fois. Ou envoyez les mêmes données en même temps.

Le code de la base de données est censé supprimer tous les enregistrements avec le même identifiant, s'il en existe déjà un. Le service n'envoie jamais deux enregistrements avec le même identifiant dans le même lot.

5
Andrew Savinykh

Si vous avez vraiment besoin d'exécuter plusieurs threads en même temps, vous pouvez activer le ignore_dup_key option sur la clé primaire.

Cela donnera simplement un avertissement au lieu d'une erreur lorsqu'une insertion entraînera une violation de clé en double. Mais au lieu d'échouer, il supprimera les lignes qui, si elles étaient insérées, entraîneraient la violation de l'unicité.

CREATE TABLE [dbo].[DeleteMe](
[id] [uniqueidentifier] NOT NULL,
[Value] [varchar](max) NULL,
CONSTRAINT [PK_DeleteMe] 
PRIMARY KEY ([id] ASC) 
WITH (IGNORE_DUP_KEY = ON));

Exemple sur sqlfiddle

Explication détaillée de Paul White sur l'option IGNORE_DUP_KEY . Merci Paul.

10
Aaron

Le nom de la table DeleteMe suggère que vous accumulez des ID dans cette table, puis supprimez périodiquement des lignes avec ces ID d'une autre table permanente. Est-ce vrai?

Si c'est vrai, vous pouvez autoriser DeleteMe à avoir des ID en double. Ayez simplement un index non unique sur id au lieu d'unique (et puisque id est uniqueidentifier, il est logique de rendre cet index non cluster également):

CREATE TABLE [dbo].[DeleteMe](
    [id] [uniqueidentifier] NOT NULL,
    [Value] [varchar](max) NULL,
)

CREATE NONCLUSTERED INDEX [IX_ID] ON [dbo].[DeleteMe]
(
    [id] ASC
)

Si vous utilisez DeleteMe pour supprimer des lignes d'un autre MainTable comme celui-ci, alors peu importe si id dans DeleteMe est unique ou non:

DELETE FROM MainTable
WHERE MainTable.ID IN (SELECT id FROM DeleteMe)

BTW, la requête pour remplir DeleteMe devient également beaucoup plus simple:

INSERT INTO DeleteMe (id, Value)
SELECT #t.id, 'test' 
FROM #t
0
Vladimir Baranov