2010-02-17 26 views
8

Tengo un SP en SQL Server que se ejecuta cientos de veces por minuto y necesita verificar el tráfico entrante en una base de datos. Por el momento se hace lo siguiente¿Qué es más rápido, EXISTE antes o después del INSERT?

INSERT INTO table 
SELECT @value1,@value2 WHERE NOT EXISTS 
(SELECT * FROM table WHERE value1 = @value1 AND value2 = @value2); 

Sin embargo, yo también podría ir con

IF NOT EXISTS(SELECT * FROM table WHERE value1 = @value1 AND value2 = @value2)  
    INSERT INTO table (value1,value2) VALUES (@value1,@value2); 

Lo que sería más rápido? Me da la sensación de que no hay mucha diferencia entre ellos, pero históricamente no soy muy bueno en TSQL ... =/

ACTUALIZACIÓN: Whoops ... significa que el EXISTS usa más de 1 valor para encontrar si un existe registro, por lo que una restricción única no funcionará. Editó la muestra para reflejar que ...

+0

http://stackoverflow.com/questions/2276023/t-sql-insert-or-update –

+0

@carlos: Esa es realmente una pregunta diferente, aunque algo relacionada. –

+1

Su segunda opción simplemente no es segura. Un 'INSERT' concurrente puede ocurrir entre las instrucciones 'IF' y' INSERT'. – Quassnoi

Respuesta

1

Después de la adición de un tropecientos comentarios sobre esta pregunta y sus respuestas, voy a tener mi propia ir en contestarla.

yo no esperaría ninguna diferencia importante en el rendimiento entre los dos propuestos propuesto en la pregunta original. Por un lado, como señaló Ray, el segundo enfoque podría evitarle hacer algunos preparativos para el inserto, pero, por otro lado, un RDBMS generalmente funciona mejor con las instrucciones de lote, como en la primera solución.

KM y DVK sugieren agregar una restricción UNIQUE, lo que hará que la prueba de exclusividad sea implícita, pero requerirá que agregue algún tipo de manejo de errores alrededor de su declaración INSERT. Me es difícil ver por qué esto debería agregar un rendimiento adicional, suponiendo que ya tiene un índice que cubre las dos columnas. Si no tiene dicho índice, agréguelo y reconsidere su necesidad de más rendimiento.

Si el control de la unicidad se realiza explícita o implícita no debería importar que yo sepa.Si se gana algo haciendo que la verificación se realice "dentro" del estómago del DBMS, esa ganancia podría ser consumida por la sobrecarga asociada con los errores de elevación y manejo cuando existen duplicados.


El resultado final: Suponiendo un índice ya está en su lugar, si usted todavía se encuentra lusting para el rendimiento, mi recomendación es que realice pruebas empíricas sobre las tres soluciones sugeridas. Prepare un pequeño programa que simule los datos de entrada esperados y elimine cada una de las tres soluciones con unos pocos miles de millones de filas, incluida una cantidad plausible de duplicados. hacer esto, asegúrese de publicar sus resultados :-)

+0

Gracias, actualmente hay un índice en cada columna pero no un índice de columna múltiple que cubra ambos. Creo que iré con eso y añadiré una restricción única. No sabía que podría agregar una restricción única que abarca múltiples columnas. – roryok

+0

@roryok: de hecho, puede cambiar su índice a un 'INDICE ÚNICO', será el mismo. – Quassnoi

+1

¡Gracias chicos! ¡Publicación por primera vez en StackOverflow y me encanta! – roryok

0

Si desea que los valores sean únicos, ¿por qué no simplemente crear una restricción única en el valor, hacer un INSERTO sin SELECCIONAR y manejar correctamente el error de violación de restricción?

Eso sería más rápido que cualquiera de estos enfoques.

Además, su primer enfoque no funciona - por el momento de llegar a seleccionar, ya insertó el valor de modo de seleccionar, obviamente, encontrar lo que acaba de insertar.

+2

Creo que estás equivocado en tu último párrafo. La cláusula 'WHERE' se une al' SELECT', que de hecho se ejecuta "antes" de la inserción. –

+0

Bueno, creo que cometí mi primer error de StackOverflow, simplifiqué esos ejemplos. Realmente está buscando un registro con DOS valores para que los registros no sean únicos ... Lo editaré para reflejar eso. Puedo garantizar al 100% que el primer método funciona, lo he probado muchas veces. – roryok

+2

Agregar una 'restricción única' que abarca múltiples columnas no debería ser un problema. –

0

Si tuviera que adivinar, yo supongo que la segunda opción sería más rápido. El servidor sql no tendría que hacer ningún tipo de configuración para la inserción si la existente falla, mientras que en la primera, podría buscar algunos nombres de tablas y campos y prepararse para una inserción que nunca ocurre. Sin embargo, lo probaría en el analizador de consultas y vería lo que dice el plan.

+0

Por otro lado, la primera opción es una declaración de lote único, mientras que la segunda es varias declaraciones en un estilo más de procedimiento. Un RDBMS suele ser muy eficiente con las declaraciones de lotes, y menos con el código de procedimiento/imperativo. Dicho esto, no tengo idea de cuál de las dos afirmaciones de la mano es la mejor :) –

1

simplemente lo hacen, e ignorar cualquier error (supone una restricción única en valor) ...

BEGIN TRY 
    INSERT INTO Table (value) VALUES (@value); 
END TRY 
BEGIN CATCH 
    PRINT 'it was already in there!' 
END CATCH 

Desde este corre cientos de veces por minuto, sugerencias de bloqueo, debe añadirse a los selecciona y una transacción a avoid a race condition

(SELECT * FROM Table WITH (UPDLOCK, HOLDLOCK) WHERE value = @value); 

sin embargo, mi idea propuesta de basta con insertar y hacer caso omiso de cualquier error de restricción duplicado evitaría una condición de carrera también.

+0

Gracias KM, estoy usando (NOLOCK) en la selección en este momento ... ¡debería haberlo mencionado también! – roryok

+0

@roryok, si se hacen dos llamadas a este SQL al mismo tiempo (usted dice que se ejecuta cientos de veces por minuto) que tienen los mismos valores, posiblemente no podrían seleccionar ninguna fila existente y ambas intentar insertarlas y así crear duplicados, derrotando sus intenciones –

+1

Tenga en cuenta que la excepción condenará la transacción si se genera dentro del desencadenador. – Quassnoi

3

En un entorno casi simultánea, concurrente INSERT puede suceder en el medio IF NOT EXISTS y INSERT en su segunda consulta.

Su primera consulta colocará los bloqueos compartidos en el registro que examina, que no se levantará hasta el final de la consulta, por lo que será imposible insertar un nuevo registro hasta que se ejecute la consulta.

Sin embargo, no debe confiar únicamente en este comportamiento. Coloque una restricción adicional UNIQUE en el value.

No solo hará que la base de datos sea más uniforme, sino que creará un índice que hará que la primera consulta sea más rápida.

+2

+1 por mencionar el riesgo de una condición de carrera. –

5

Ambas variantes son incorrectas. Deberá insertar pares de duplicados @ value1, @ value2, garantizados.

La forma correcta de manejar esto es para hacer cumplir una restricción única en dos columnas e insertar siempre y manejar la violación de restricción:

ALTER TABLE Table ADD CONSTRAINT uniqueValue1Value UNIQUE (value1, values2); 

e insertar:

BEGIN TRY 
    INSERT INTO Table (value1, value2) VALUES (@value1, @value2); 
END TRY 
BEGIN CATCH 
    DECLARE @error_number int, @error_message NVARCHAR(4000), @xact_state INT; 
    SET @error_number = ERROR_NUMBER(); 
    SET @error_message = ERROR_MESSAGE(); 
    SET @xact_state = XACT_STATE(); 
    IF (@xact_state = -1) 
    BEGIN 
    ROLLBACK TRANSACTION; 
    END 
    IF (@error_number != 2627) /* 2627 is ' Cannot insert duplicate key in object ...' */ 
    BEGIN 
     RAISERROR(N'Error inserting into Table: %i %s', 16,1, @errror_number, @error_message); 
    END 
ENd CATCH 

Mientras estos pueden parecer complicados, uno tiene que tener en cuenta un pequeño detalle llamado corrección. Esto es mucho más simple si se compara con una solución basada en pistas de bloqueo. Esta es también la solución más eficaz: solo busca uno. Todas las demás soluciones necesitan al menos dos búsquedas (una para validar que se puede insertar, una para insertar).

+0

Gracias Remus. ¿Qué transacción afectará esa ROLLBACK TRANSACTION? El SP completo está encerrado en una declaración de transacción que incluye algunas más de estas instrucciones de inserción, no quiero terminar todo si el valor ya está allí ... – roryok

+1

Cuando XACT_STATE() es -1 no tiene otra opción , significa que la transcation está condenada y * debe * deshacerse. Una violación de restricción única no causará una reversión, esto sucederá solo si se encuentra un error más grave, como por ejemplo, falta de espacio en el disco. –

+0

Solo 3 años de retraso en la discusión, pero me preguntaba sobre su afirmación de que los duplicados están garantizados: ¿cómo puede el INSERTAR ... DONDE NO EXISTE() causar duplicados? Como INSERT & SELECT están ambos en la misma transacción, este último bloquea el registro y no lo libera hasta que INSERT finaliza. Esto también impide que otras conexiones hagan el mismo INSERT ya que no pueden obtener un bloqueo hasta que finalice la inserción. He estado haciendo esto por años y nunca tuve ningún problema con eso. AFAIK también es el único enfoque válido para usar al insertar inserciones múltiples. – deroby

Cuestiones relacionadas