EDITAR: para prevenir race conditions en entornos concurrentes, utilice WITH (UPDLOCK)
en la subconsulta correlacionada o EXCEPT
'd SELECT
. El script de prueba que escribí a continuación no lo requiere, ya que usa tablas temporales que solo son visibles para la conexión actual, pero en un entorno real, que opera en contra de tablas de usuarios, sería necesario.
MERGE
no requiere UPDLOCK
.
Inspirado por mcl de respuesta re: índice único & vamos a la base de datos emite un error, decidí referencia conditional inserts vs try/catch.
Los resultados parecen apoyar la inserción condicional sobre try/catch, pero YMMV. Es un escenario muy simple (una columna, una pequeña mesa, etc), ejecutado en una máquina, etc.
Éstos son los resultados (SQL Server 2008, construir 10.0.1600.2):
duplicates (short table)
try/catch: 14440 milliseconds/100000 inserts
conditional insert: 2983 milliseconds/100000 inserts
except: 2966 milliseconds/100000 inserts
merge: 2983 milliseconds/100000 inserts
uniques
try/catch: 3920 milliseconds/100000 inserts
conditional insert: 3860 milliseconds/100000 inserts
except: 3873 milliseconds/100000 inserts
merge: 3890 milliseconds/100000 inserts
straight insert: 3173 milliseconds/100000 inserts
duplicates (tall table)
try/catch: 14436 milliseconds/100000 inserts
conditional insert: 3063 milliseconds/100000 inserts
except: 3063 milliseconds/100000 inserts
merge: 3030 milliseconds/100000 inserts
Aviso, incluso en inserciones únicas, hay ligeramente más sobrecarga para intentar/atrapar que una inserción condicional. Me pregunto si esto varía según la versión, la CPU, el número de núcleos, etc.
No comparé las inserciones condicionales IF
, solo WHERE
. Supongo que la variedad IF
mostraría más sobrecarga, ya que a) tendría dos instrucciones, yb) tendría que ajustar las dos declaraciones en una transacción y establecer el nivel de aislamiento en serializable (!). Si alguien quería para probar esto, necesitaría cambiar la tabla temporal a una tabla de usuario normal (serializable no se aplica a las tablas temporales locales).
Aquí está la secuencia de comandos:
-- tested on SQL 2008.
-- to run on SQL 2005, comment out the statements using MERGE
set nocount on
if object_id('tempdb..#temp') is not null drop table #temp
create table #temp (col1 int primary key)
go
-------------------------------------------------------
-- duplicate insert test against a table w/ 1 record
-------------------------------------------------------
insert #temp values (1)
go
declare @x int, @y int, @now datetime, @duration int
select @x = 1, @y = 0, @now = getdate()
while @y < 100000 begin
set @y = @y+1
begin try
insert #temp select @x
end try
begin catch end catch
end
set @duration = datediff(ms,@now,getdate())
raiserror('duplicates (short table), try/catch: %i milliseconds/%i inserts',-1,-1,@duration,@y) with nowait
go
declare @x int, @y int, @now datetime, @duration int
select @x = 1, @y = 0, @now = getdate()
while @y < 100000 begin
set @y = @y+1
insert #temp select @x where not exists (select * from #temp where col1 = @x)
end
set @duration = datediff(ms,@now,getdate())
raiserror('duplicates (short table), conditional insert: %i milliseconds/%i inserts',-1,-1,@duration, @y) with nowait
go
declare @x int, @y int, @now datetime, @duration int
select @x = 1, @y = 0, @now = getdate()
while @y < 100000 begin
set @y = @y+1
insert #temp select @x except select col1 from #temp
end
set @duration = datediff(ms,@now,getdate())
raiserror('duplicates (short table), except: %i milliseconds/%i inserts',-1,-1,@duration, @y) with nowait
go
-- comment this batch out for SQL 2005
declare @x int, @y int, @now datetime, @duration int
select @x = 1, @y = 0, @now = getdate()
while @y < 100000 begin
set @y = @y+1
merge #temp t using (select @x) s (col1) on t.col1 = s.col1 when not matched by target then insert values (col1);
end
set @duration = datediff(ms,@now,getdate())
raiserror('duplicates (short table), merge: %i milliseconds/%i inserts',-1,-1,@duration, @y) with nowait
go
-------------------------------------------------------
-- unique insert test against an initially empty table
-------------------------------------------------------
truncate table #temp
declare @x int, @now datetime, @duration int
select @x = 0, @now = getdate()
while @x < 100000 begin
set @x = @x+1
insert #temp select @x
end
set @duration = datediff(ms,@now,getdate())
raiserror('uniques, straight insert: %i milliseconds/%i inserts',-1,-1,@duration, @x) with nowait
go
truncate table #temp
declare @x int, @now datetime, @duration int
select @x = 0, @now = getdate()
while @x < 100000 begin
set @x = @x+1
begin try
insert #temp select @x
end try
begin catch end catch
end
set @duration = datediff(ms,@now,getdate())
raiserror('uniques, try/catch: %i milliseconds/%i inserts',-1,-1,@duration, @x) with nowait
go
truncate table #temp
declare @x int, @now datetime, @duration int
select @x = 0, @now = getdate()
while @x < 100000 begin
set @x = @x+1
insert #temp select @x where not exists (select * from #temp where col1 = @x)
end
set @duration = datediff(ms,@now,getdate())
raiserror('uniques, conditional insert: %i milliseconds/%i inserts',-1,-1,@duration, @x) with nowait
go
truncate table #temp
declare @x int, @now datetime, @duration int
select @x = 0, @now = getdate()
while @x < 100000 begin
set @x = @x+1
insert #temp select @x except select col1 from #temp
end
set @duration = datediff(ms,@now,getdate())
raiserror('uniques, except: %i milliseconds/%i inserts',-1,-1,@duration, @x) with nowait
go
-- comment this batch out for SQL 2005
truncate table #temp
declare @x int, @now datetime, @duration int
select @x = 1, @now = getdate()
while @x < 100000 begin
set @x = @x+1
merge #temp t using (select @x) s (col1) on t.col1 = s.col1 when not matched by target then insert values (col1);
end
set @duration = datediff(ms,@now,getdate())
raiserror('uniques, merge: %i milliseconds/%i inserts',-1,-1,@duration, @x) with nowait
go
-------------------------------------------------------
-- duplicate insert test against a table w/ 100000 records
-------------------------------------------------------
declare @x int, @y int, @now datetime, @duration int
select @x = 1, @y = 0, @now = getdate()
while @y < 100000 begin
set @y = @y+1
begin try
insert #temp select @x
end try
begin catch end catch
end
set @duration = datediff(ms,@now,getdate())
raiserror('duplicates (tall table), try/catch: %i milliseconds/%i inserts',-1,-1,@duration,@y) with nowait
go
declare @x int, @y int, @now datetime, @duration int
select @x = 1, @y = 0, @now = getdate()
while @y < 100000 begin
set @y = @y+1
insert #temp select @x where not exists (select * from #temp where col1 = @x)
end
set @duration = datediff(ms,@now,getdate())
raiserror('duplicates (tall table), conditional insert: %i milliseconds/%i inserts',-1,-1,@duration, @y) with nowait
go
declare @x int, @y int, @now datetime, @duration int
select @x = 1, @y = 0, @now = getdate()
while @y < 100000 begin
set @y = @y+1
insert #temp select @x except select col1 from #temp
end
set @duration = datediff(ms,@now,getdate())
raiserror('duplicates (tall table), except: %i milliseconds/%i inserts',-1,-1,@duration, @y) with nowait
go
-- comment this batch out for SQL 2005
declare @x int, @y int, @now datetime, @duration int
select @x = 1, @y = 0, @now = getdate()
while @y < 100000 begin
set @y = @y+1
merge #temp t using (select @x) s (col1) on t.col1 = s.col1 when not matched by target then insert values (col1);
end
set @duration = datediff(ms,@now,getdate())
raiserror('duplicates (tall table), merge: %i milliseconds/%i inserts',-1,-1,@duration, @y) with nowait
go
Recuerde que el segundo enfoque debe incluirse en una transacción, otra sabio, podrías tener problemas de concurrencia. –
¿no puedes simplemente crear un índice único? no tengo experiencia en ms sql pero creo que debería haber tales indeces –
@valya: Es curioso cómo las personas dudan de que SQL Server pueda hacer incluso las cosas más simples. Ni siquiera estoy seguro de si puede implementar un motor de base de datos relacional * sin * que soporte índices únicos. – Tomalak