2009-11-06 6 views
10

빠른 삽입을 원하지만 중복을 방지하려면 테이블에 넣으십시오. 인수로 MarketPrices를 호출 할 수 있습니다. 두 가지 방법으로 실험하고 있지만 더 빠른 벤치마킹 방법을 모릅니다.SQL INSERT하지만 중복을 피하십시오.

INSERT INTO MarketPrices (SecurityCode, BuyPrice, SellPrice, IsMarketOpen) 
SELECT @SecurityCode, @BuyPrice, @SellPrice, @IsMarketOpen 
EXCEPT 
SELECT SecurityCode, BuyPrice, SellPrice, j.bool as IsActive FROM MarketPrices 
CROSS JOIN (SELECT 0 as bool UNION SELECT 1 as bool) as j 

OR

DECLARE @MktId int 
SET @MktId = (SELECT SecurityId FROM MarketPrices 
       where SecurityCode = @SecurityCode 
       and [email protected] 
       and SellPrice = @SellPrice) 

IF (@MktId is NULL) 
BEGIN 
    INSERT INTO MarketPrices (SecurityCode, BuyPrice, SellPrice, IsMarketOpen) 
    VALUES 
    (@SecurityCode,@BuyPrice, @SellPrice, @IsMarketOpen) 
END 

@whatever는 저장 프로 시저의 입력 파라미터라고 가정한다.

BuyPrice 또는 SellPrice 또는 둘 다 이전의 모든 발생과 다른 경우 모든 SecurityCode에 대해 새 레코드를 삽입 할 수 있기를 원합니다. 나는 IsMarketOpen을 걱정하지 않는다.

위의 방법 중 하나에 대해 눈부신 바보 같은 것이 있습니까? 하나가 다른 것보다 빠릅니까?

+3

번째 접근 기억 거래 내에서 묶어야한다. 현명한 경우 동시성 문제가 발생할 수 있습니다. –

+1

고유 한 색인을 만들 수 없습니까? 나는 ms SQL에 대한 경험이 없지만 그러한 indeces가 있어야한다고 생각한다. –

+3

@valya : 사람들이 SQL Server가 가장 단순한 작업을 수행 할 수 있는지 의심스러워하는 방법. 고유 한 인덱스를 지원하지 않고 관계형 데이터베이스 엔진 *을 구현할 수 있는지 확실하지 않습니다. – Tomalak

답변

11

EDIT : 동시 환경 race conditions 방지 상관 하위 쿼리 WITH (UPDLOCK) 사용하거나 EXCEPT 뭔는 SELECT d는. 아래에 쓴 테스트 스크립트는 현재 연결에서만 볼 수있는 임시 테이블을 사용하기 때문에 필요하지 않지만 실제 환경에서는 사용자 테이블에 대해 작동하는 것이 필요합니다.

MERGE에는 UPDLOCK이 필요하지 않습니다. MCL의 대답을 다시에서 영감을


: 고유 인덱스 & 나는 벤치 마크 conditional insertstry/catch하기로 결정, 데이터베이스 오류가 발생 할 수 있습니다.

결과는 try/catch에서 조건부 삽입을 지원하지만 YMMV는 지원하는 것으로 보입니다. 그것은 등

여기 결과 (SQL 서버 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 

공지 사항, 심지어 독특한 인서트에 대해서도 약간 이상의 조건부 삽입보다 더 많은 오버 헤드가 필요합니다. 버전, CPU, 코어 수 등에 따라 차이가 있는지 궁금합니다.

IF 조건부 삽입을 벤치 마크하지 않았습니다. 단지 WHERE입니다. 나는 IF 버라이어티가 더 많은 오버 헤드를 보여줄 것이라고 가정한다. 왜냐하면 a) 두 개의 명령문을 갖고 있기 때문이다. 그리고 b) 트랜잭션에서 두 개의 명령문을 래핑하고 격리 수준을 직렬 가능 (!)으로 설정해야한다. 누군가 을 테스트하려면 임시 테이블을 일반 사용자 테이블로 변경해야합니다 (직렬 테이블은 로컬 임시 테이블에 적용되지 않음). 당신이 트랩 중복에 필요하지 않은 경우, 당신은 항상 true로 설정 "중복 무시"와 고유 인덱스를 만들 수 있습니다

-- 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 
+1

여기에서 고유 인덱스를 사용하는 주요 이유는 데이터 무결성을 보장하기 위해서입니다. 나는 try/catch 블록에 실패한 삽입이 대부분의 응용 프로그램에서 병목 현상이 될 수 없다고 생각합니다. 특히 중복을 삽입하려는 시도가 많지 않은 경우 (벤치 마크에서 비슷한 성능을 보여 주므로 케이스). 하지만 데이터 모델을 적용하지 않으면 문제가 발생할 수 있다고 생각됩니다. 또한 SQL Server 2008에서는 이러한 다른 전략 중 하나를 통해 MERGE를 사용하는 방법을 모색하는 것이 좋습니다. – mlibby

+1

@mcl re : 고유 인덱스, 전적으로 동의합니다. 데이터 무결성을위한 인덱스가 있어야하며, 합리적인 성능을 원하면 인덱스가 필요합니다. re : MERGE, 방금 테스트했는데 모든 시나리오의 조건부 삽입과 비슷하게 * 매우 * 수행합니다. –

+0

여러분, 두 가지 대답을 모두 받아 들일 수 있기를 바랍니다. 필자는 데이터 무결성을 위해 고유 한 인덱스를 삽입 한 다음 조건부 삽입을 사용하여 성능 및 가독성 측면에서 최상으로 보일 것입니다. – Ravi

6

편집 : 동시 환경에서 race conditions을 방지하려면 상관 하위 쿼리에서 WITH (UPDLOCK)을 사용하십시오. 당신의 모든 필드에 널 (NULL) 인 경우, 당신은 상태로 그를 추가해야

INSERT INTO MarketPrices (SecurityCode, BuyPrice, SellPrice, IsMarketOpen) 
SELECT @SecurityCode, @BuyPrice, @SellPrice, @IsMarketOpen 
WHERE NOT EXISTS (
    SELECT * FROM MarketPrices WITH (UPDLOCK) 
    WHERE SecurityCode = @SecurityCode 
    AND BuyPrice = @BuyPrice 
    AND SellPrice = @SellPrice 
) 

:


나는이 표준 방법이 될 것이라고 생각합니다.

첫 번째 방법은 흥미 롭습니다. 그러나 예외적 인 요구 사항은 당신이 농구를 뛰어 넘는 것입니다. 이 방법은 본질적으로 동일하지만 열 일치 문제를 해결할 수 있습니다. 또한

는 :

INSERT INTO MarketPrices (SecurityCode, BuyPrice, SellPrice, IsMarketOpen) 
SELECT SecurityCode, BuyPrice, SellPrice, @IsMarketOpen 
FROM (
    SELECT @SecurityCode, @BuyPrice, @SellPrice 
    EXCEPT 
    SELECT SecurityCode, BuyPrice, SellPrice FROM MarketPrices WITH (UPDLOCK) 
) a (SecurityCode, BuyPrice, SellPrice) 

이 경우를 제외하고 좋은 점은 당신의 부분에 여분의 코딩없이 널 (null)을 처리하는 것입니다. 첫 번째 예제에서 똑같은 결과를 얻으려면 평등, 장시간뿐만 아니라 NULL에 대해서도 각 쌍을 테스트해야합니다.

두 번째 방법은 괜찮지 만 변수가 필요하지 않습니다. Tomalak의 해결책을 참조하십시오. 그는 멋지게 정리했습니다. 또한, 동시 삽입의 가능성을 명시 적으로 처리해야합니다 (우려되는 경우).

3

언제든지 시맨틱 한 해결책을 찾아 보겠습니다. 당신의 두 제안은 저에게 아주 불명확하게 보입니다 (후자는 이전보다 낫습니다). EXISTS 쿼리가 합리적으로 빨리 가야 SecurityCode, BuyPrice, SellPrice 통해 대기업 지수

IF NOT EXISTS (
    SELECT 1 
    FROM MarketPrices 
    WHERE SecurityCode = @SecurityCode 
     AND BuyPrice = @BuyPrice 
     AND SellPrice = @SellPrice 
) 
BEGIN 
    INSERT MarketPrices 
    (SecurityCode, BuyPrice, SellPrice, IsMarketOpen) 
    VALUES 
    (@SecurityCode, @BuyPrice, @SellPrice, @IsMarketOpen) 
END 

.

벤치마킹 타이밍은 WHILE 루프입니다. 그것을 시험하고 너 자신을 위해보십시오.

2

다른 옵션 : 해당 필드 (SecurityCode, BuyPrice, SellPrice)에 고유 색인을 작성하고 간단한 삽입을 실행 한 다음 레코드가 중복 레코드인지 여부를 데이터베이스가 판별하게하십시오. 중복 삽입을 시도하면 삽입이 실패합니다.

고유성을 보장하기 위해 코드 (외부 언어 또는 SQL proc)를 사용하면 충분히 엄격하지 않으므로 방지 할 수있는 매우 복잡한 코드가 생성됩니다.

+0

나는 당신이 맞을지도 모른다고 생각합니다. 특히 동시 삽입물 인 경우 – Ravi

+0

이 벤치마킹에 관심이 있습니다. 조건부 삽입의 WHERE 절이나 TRY/CATCH 블록의 예외 처리가 더 많은 오버 헤드를 갖는 고유 색인을 가정합니까? 삽입물의 99 %가 중복되지 않을 것으로 예상된다면 TRY/CATCH 블록이 더 효율적일 수 있다고 생각합니다. –

+0

내가 집에 갈 때 나는 정확하게 그 일을 할 것이다. 여기에 결과를 게시 할 것이다. – Ravi

0

: 여기

는 스크립트입니다. SQL Server가이를 처리합니다.

1

아래의 답변을 Only inserting a row if it's not already there에서 Peter Radocchia의 탁월한 답변에 추가했습니다.

중요한 점은 실제 충돌이 없을 때 race safe with try/catch 기술을 사용하여 소폭 race safe with updlock, holdlock 기술보다 (~ 1 %) 빠른 것입니다 (당신이 충돌은 매우 드문 것으로 기대 즉, - 이것은 uniques 시나리오입니다), 그리고이다 (항상 duplicates 시나리오입니다.) 충돌이있을 때 조금 느린 (~ 20 %). 이것은 잠금 에스컬레이션과 같은 복잡한 문제를 고려하지 않습니다.

결과는 다음과 같습니다 (SQL Server 2014, 빌드 12.0.2000).8)

duplicates (short table)  
    try/catch:      15546 milliseconds/100000 inserts 
    conditional insert:    1460 milliseconds/100000 inserts 
    except:       1490 milliseconds/100000 inserts 
    merge:       1420 milliseconds/100000 inserts 
    race safe with try/catch:   1650 milliseconds/100000 inserts 
    race safe with updlock, holdlock: 1330 milliseconds/100000 inserts 

uniques 
    try/catch:      2266 milliseconds/100000 inserts 
    conditional insert:    2156 milliseconds/100000 inserts 
    except:       2273 milliseconds/100000 inserts 
    merge:       2136 milliseconds/100000 inserts 
    race safe with try/catch:   2400 milliseconds/100000 inserts 
    race safe with updlock, holdlock: 2430 milliseconds/100000 inserts 

    straight insert:     1686 milliseconds/100000 inserts 

duplicates (tall table) 
    try/catch:      15826 milliseconds/100000 inserts 
    conditional insert:    1530 milliseconds/100000 inserts 
    except:       1506 milliseconds/100000 inserts 
    merge:       1443 milliseconds/100000 inserts 
    race safe with try/catch:   1636 milliseconds/100000 inserts 
    race safe with updlock, holdlock: 1426 milliseconds/100000 inserts 

중복 (짧은 테이블) 섹션

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 where not exists (select * from #temp where col1 = @x) 
    end try 
    begin catch 
    if error_number() <> 2627 
     throw 
    end catch 
end 
set @duration = datediff(ms,@now,getdate()) 
raiserror('duplicates (short table), race safe with 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 with (updlock, holdlock) where col1 = @x) 
end 
set @duration = datediff(ms,@now,getdate()) 
raiserror('duplicates (short table), race safe with updlock, holdlock: %i milliseconds/%i inserts',-1,-1,@duration, @y) 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 where not exists (select * from #temp where col1 = @x) 
    end try 
    begin catch 
    if error_number() <> 2627 
     throw 
    end catch 
end 
set @duration = datediff(ms,@now,getdate()) 
raiserror('uniques, race safe with 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 with (updlock, holdlock) where col1 = @x) 
end 
set @duration = datediff(ms,@now,getdate()) 
raiserror('uniques, race safe with updlock, holdlock: %i milliseconds/%i inserts',-1,-1,@duration, @x) 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 
    begin try 
    insert #temp select @x where not exists (select * from #temp where col1 = @x) 
    end try 
    begin catch 
    if error_number() <> 2627 
     throw 
    end catch 
end 
set @duration = datediff(ms,@now,getdate()) 
raiserror('duplicates (tall table), race safe with 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 with (updlock, holdlock) where col1 = @x) 
end 
set @duration = datediff(ms,@now,getdate()) 
raiserror('duplicates (tall table), race safe with updlock, holdlock: %i milliseconds/%i inserts',-1,-1,@duration, @y) with nowait 
go 
관련 문제