EDIT:防止race conditions併發環境中,在相關子查詢使用WITH (UPDLOCK)
或EXCEPT
倒是SELECT
。我在下面寫的測試腳本不需要它,因爲它使用只對當前連接可見的臨時表,但是在真實環境中,對用戶表進行操作時,這是非常必要的。
MERGE
不需要UPDLOCK
。
通過MCL的回答再次啓發:唯一索引&讓數據庫拋出一個錯誤,我決定把基準conditional inserts與try/catch。
結果似乎支持了try/catch語句條件插入,但情況因人而異。這是一個非常簡單的場景(一列,小桌子等),一臺機器上執行,等等
下面是結果(SQL Server 2008中,構建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
通知,即使在獨特的插入上,也有略微比嘗試/ catch更多的開銷比條件插入。我想知道這是否因版本,CPU,內核數量等而異。
我沒有基準IF
條件插入,只是WHERE
。我認爲IF
變種會顯示更多的開銷,因爲a)你會有兩個語句,b)你需要將兩個語句包裝在一個事務中,並將隔離級別設置爲可序列化(!)。如果有人想要來測試這個,你需要將臨時表更改爲常規用戶表(可序列化不適用於本地臨時表)。
下面是腳本:
-- 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
記住,第二方法應該被包含在一個交易中,其他明智的你可能會遇到併發問題。 – 2009-11-06 16:37:57
難道你不能只是創建一個唯一的索引?我沒有在MS SQL的經驗,但我認爲應該有這樣的傾向 – 2009-11-06 16:41:19
@valya:有趣的人們如何懷疑SQL Server甚至可以做到最簡單的事情。我甚至不確定是否可以在沒有*支持唯一索引的情況下實現關係數據庫引擎*。 – Tomalak 2009-11-06 16:50:31