Insertar si no existe, simultáneamente

13

Tengo problemas de concurrencia con mis inserciones en un procedimiento almacenado. La parte relevante del procedimiento es 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

Cuando ejecutamos 3 o 4 de estos procesos almacenados simultáneamente, obtenemos múltiples inserciones en ocasiones.

Estoy planeando arreglar esto así:

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 pregunta es, ¿cómo insertar simultáneamente sin duplicados en el servidor SQL? El hecho de que tenga que usar TOP para insertar solo una vez me molesta.

Chris
fuente
1
No tienes que usar TOP. Elimine la referencia de tabla FROM de la instrucción SELECT.
ErikE
@GSerg Creo que tienes razón.
Chris

Respuestas:

7

Podría usar una declaración de fusión con una serializablepista.

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
fuente
¿Puso a prueba su enfoque a partir de dos o más conexiones?
AK
2
@AlexKuznetsov: lo hice hace un tiempo para otra pregunta sobre SO. Usé dos pestañas en SSMS. Primero probó el insert ... where not exist ...patrón y descubrió que puede obtener puntos muertos y violaciones de claves, por lo que allí era necesario usar updlock y serializable. Luego probé la declaración de fusión y pensé que manejaría las cosas un poco mejor, y lo hizo porque no había puntos muertos, pero todavía tenía que usar serializable para no tener violaciones clave.
Mikael Eriksson
1
Esta es una respuesta realmente asombrosa.
Chris Marisic
5

Si no desea duplicados en la columna 'otro valor', puede hacerlo creando un unique constrainten esa columna. La consulta sería:

 ALTER TABLE table1
 ADD CONSTRAINT unique_c_othervalue UNIQUE(othervalue)

Esto arrojaría un error si una consulta intentara insertar un valor duplicado en la columna 'otro valor'.

StanleyJohns
fuente
¿Cómo funcionaría eso si la restricción única es una tupla de dos filas?
Chris
1
@Chris ¿Cómo tiene una restricción única que abarca filas?
Aaron Bertrand
@ Aaron, probablemente tengo mi terminología desactivada, pero tenemos dos filas que juntas deben ser únicas. No creo que se aplique en nuestro esquema.
Chris
2

Use una restricción única como la que recomienda @StanleyJohns. Luego, use BEGIN TRY END TRY alrededor de su declaración de inserción.

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
fuente