Insérer s'il n'existe pas, simultanément

13

J'ai des problèmes de concurrence avec mes insertions dans une procédure stockée. La partie pertinente de la procédure est la suivante:

select @_id = Id from table1 where othervalue = @_othervalue
IF( @_id IS NULL)
BEGIN
    insert into table1 (othervalue) values (@_othervalue)
    select @_id = Id from table1 where othervalue = @_othervalue
END

Lorsque nous exécutons 3 ou 4 de ces proc stockés simultanément, nous obtenons plusieurs insertions à l'occasion.

Je prévois de résoudre ce problème comme ceci:

insert into table1 (othervalue) 
    select TOP(1) @_othervalue as othervalue from table1 WITH(UPDLOCK) 
    where NOT EXISTS ( select * from table1 where othervalue = @_othervalue )

select @_id = Id from table1 where othervalue = @_othervalue

La question est, est-ce que comment insérer simultanément sans doublons dans le serveur SQL? Le fait que je doive utiliser TOP pour ne l'insérer qu'une seule fois me dérange.

Chris
la source
1
Vous n'êtes pas obligé d'utiliser TOP. Supprimez la référence de la table FROM de l'instruction SELECT.
ErikE
@GSerg Je pense que vous avez raison.
Chris

Réponses:

7

Vous pouvez utiliser une instruction de fusion avec serializableindice.

merge table1 with (serializable) as T 
using (select @_othervalue as othervalue) as S
on T.othervalue = S.othervalue
when not matched then
  insert (othervalue) values (othervalue);
Mikael Eriksson
la source
Avez-vous testé votre approche à partir de deux connexions ou plus?
AK
2
@AlexKuznetsov - Je l'ai fait il y a quelque temps pour une autre question sur SO. J'ai utilisé deux onglets dans SSMS. D'abord testé le insert ... where not exist ...modèle et constaté que vous pouvez obtenir des blocages et des violations de clé, il était donc nécessaire d'utiliser le verrouillage de mise à jour et la sérialisation. J'ai ensuite testé la déclaration de fusion et j'ai pensé qu'elle gérerait les choses un peu mieux et c'est parce qu'il n'y avait pas de blocages mais que je devais encore utiliser sérialisable pour ne pas avoir de violations clés.
Mikael Eriksson
1
C'est une réponse vraiment géniale.
Chris Marisic
5

Si vous ne voulez pas de doublons sur la colonne «autre valeur», vous pouvez le faire en créant un unique constraintsur cette colonne. La requête serait:

 ALTER TABLE table1
 ADD CONSTRAINT unique_c_othervalue UNIQUE(othervalue)

Cela renverrait une erreur si une requête tentait d'insérer une valeur en double dans la colonne «autre valeur».

StanleyJohns
la source
Comment cela fonctionnerait-il si la contrainte unique était un tuple à deux lignes?
Chris
1
@Chris Comment avez-vous une contrainte unique qui s'étend sur des lignes?
Aaron Bertrand
@Aaron J'ai probablement ma terminologie, mais nous avons deux lignes qui, ensemble, doivent être uniques. Je ne pense pas que cela soit appliqué dans notre schéma.
Chris
2

Utilisez une contrainte unique comme le recommande @StanleyJohns. Utilisez ensuite BEGIN TRY END TRY autour de votre instruction d'insertion.

select @_id = Id from table1 where othervalue = @_othervalue
IF( @_id IS NULL)
BEGIN
    BEGIN TRY
        insert into table1 (othervalue) values (@_othervalue)
        select @_id = Id from table1 where othervalue = @_othervalue        
    END TRY
    BEGIN CATCH
        select @_id = Id from table1 where othervalue = @_othervalue        
    END CATCH
END
mrdenny
la source