Inserir se não existir, simultaneamente

13

Estou tendo problemas de simultaneidade com minhas inserções em um procedimento armazenado. A parte relevante do procedimento é esta:

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

Quando executamos 3 ou 4 desses procs armazenados simultaneamente, obtemos várias inserções de vez em quando.

Estou pensando em corrigir isso assim:

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

A questão é: é assim que inserir simultaneamente sem duplicatas no sql server? O fato de eu ter que usar o TOP para inserir apenas uma vez me perturba.

Chris
fonte
1
Você não precisa usar o TOP. Remova a referência da tabela FROM da instrução SELECT.
ErikE
@ GSerg Eu acho que você está correto.
21412 Chris

Respostas:

7

Você pode usar uma instrução de mesclagem com serializabledica.

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
fonte
Você testou sua abordagem a partir de duas ou mais conexões?
AK
2
@AlexKuznetsov - Eu fiz isso há um tempo atrás para outra pergunta sobre SO. Eu usei duas guias no SSMS. Primeiro testei o insert ... where not exist ...padrão e constatei que você pode obter conflitos e violações de teclas; portanto, era necessário usar o updlock e o serializável. Depois, testei a declaração de mesclagem e pensei que ela lidaria com as coisas um pouco melhor e funcionou porque não havia impasses, mas eu ainda precisava usar serializável para não ter violações de chave.
Mikael Eriksson
1
Esta é uma resposta realmente impressionante.
Chris Marisic
5

Se você não deseja duplicatas na coluna 'othervalue', pode fazê-lo criando uma unique constraintnessa coluna. A consulta seria:

 ALTER TABLE table1
 ADD CONSTRAINT unique_c_othervalue UNIQUE(othervalue)

Isso retornaria um erro se uma consulta tentasse inserir um valor duplicado na coluna 'othervalue'.

StanleyJohns
fonte
Como isso funcionaria se a restrição exclusiva for uma tupla de duas linhas?
1828 Chris
1
@ Chris Como você tem uma restrição única que abrange linhas?
Aaron Bertrand
@ Aaron Eu provavelmente tenho minha terminologia desativada, mas temos duas linhas que juntas precisam ser únicas. Eu não acho que isso é imposto em nosso esquema.
21412 Chris
2

Use uma restrição exclusiva, como a @StanleyJohns recomenda. Em seguida, use BEGIN TRY END TRY ao redor da instrução de inserção.

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
fonte