/* 테이블생성하고 데이터넣기*/
set statistics io off
if OBJECT_ID('tbxx') is not null drop table tbxx
go
if OBJECT_ID('tbyy') is not null drop table tbyy
go
create table tbxx (aint int primary key, atext varchar(10), atext1 varchar(11), acheck tinyint);
go
create table tbyy (aint int primary key, atext varchar(10), atext1 varchar(11), acheck tinyint);
go
set nocount on
declare @a int
set @a = 1;
while @a <= 100000
begin
insert into tbxx values(@a, '','',0);
insert into tbyy values(@a, '','',0);
set @a += 1;
end
/* 돌려보기*/
set statistics io on
set nocount on
declare @a int
set @a = 1000091;
-- Merge 문
merge into dbo.tbxx as target
using(values(@a))
as source (aint)
on target.aint = source.aint
when matched then
update set atext = 'a1', atext1 = 'b1', acheck = 1
when not matched by target then
insert (aint, atext, atext1, acheck) values(aint, '','',0);
-- 옛날방식(뭐 여러가지 방법이 있겠지만..)
update dbo.tbyy set atext ='a',atext1 ='b',acheck =1 where aint =@a;
if @@ROWCOUNT=0
begin
insert into dbo.tbyy values(@a,'','',0);
end
실행 결과.
데이터가 존재하지 않는 경우
데이터가 존재하는 경우
데이터가 없는 경우는 확실히 Merge 를 사용하는 것이 유리 하다.
데이터가 있는 경우에도 io 나 계획이 똑같은 것을 보니 내부적으로 아름답게 돌려 주나 보다..
그러나 Merge가 아무리 아름답게 돌아간대도 데이터가 있는지 없는지는 판단 해야 할거 아닌가..
좀더 확실하게 테스트 해보기 위해 위와 똑같은 상황에서 인덱스만 제거 하고(풀스캔) 다시 실행..
데이터가 존재하지 않는 경우

데이터가 존재하는 경우

오 두번다 예전에 사용하던 방식이 더 좋게 나왔다.
풀스캔 하고 데이터가 있는 경우에도 기존 방식이 더 좋게 나왔다.
--Merge
Table 'tbxx'. Scan count 1, logical reads 365, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 1, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
--기존 쿼리
Table 'tbyy'. Scan count 1, logical reads 364, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tbyy'. Scan count 0, logical reads 1, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
특히 풀스캔을 하고 데이터가 있는 경우에는 io 도 기존 방식이 더 좋게 나왔다.
--Merge
Table 'tbxx'. Scan count 1, logical reads 365, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 1, logical reads 5, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
--기존 쿼리
Table 'tbyy'. Scan count 1, logical reads 364, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Merge 는 match 와 not match 두번 모두 체크 할테고..
기존 쿼리는 update 하고 @@ROWCOUNT 가 0 이면 상대적으로 비용이 적은 INSERT 한번 하고 끝날테니..
리소스를 더 소비하는건 맞는 말이 되겠지.
그리고 using 절 에 위에서 테스트한 변수 말고 select 절도 올수 있다.
하지만 with(readuncommitted) 같은 잠금에 대한 힌트를 사용한다면 데이터의 무결성이 깨질 수도 있다.
/* 테이블 한개 더 만들고 */
ifOBJECT_ID('tbz')isnotnulldroptabletbz
go
createtabletbz (aint int,acheck int);
go
declare@a int
set@a =1;
while@a <=10
begin
insertintotbz values(@a,0);
set@a +=1;
end
go
/*전체데이터의 반을 tbx 와 다르게 변경*/
updatetbz setaint *=10 whereaint >5
/* 첫번째 세션에서 실행 */
begin tran
update tbz set acheck = 1 where aint = 1
/* 두번째 세션에서 실행 */
begin tran
merge into dbo.tbz as target
using
(
select
a.aint aintz
, b.aint aintx
, isnull(a.acheck,0) + isnull(b.acheck,0)
from dbo.tbz a with(readuncommitted)
left outer join
dbo. tbx b on
a.aint = b.aint
)
as source (aintz, aintx, sumdata)
on target.aint = source.aintz
when matched then
update set acheck += sumdata
when not matched by target then
insert (aint, acheck) values(aintz, 0);
여기서 첫번째 세션에서 실행한 구문을 ROLLBACK 시키고
두번째 세션의 구문을 COMMIT 해버리면
tbz 의 값이 ROLLBACK 되기전 값으로 계산된 값이 UPDATE 되어진다.
using 절에 select 를 사용 할때는 이점의 유의 해야지 싶다.
또한 현재 database 의 TRANSACTION ISOLATION LEVEL 에 따라서 도
의도하지 않은 값이 나 올 수 있으니 요것도 주의 해야지~
뭐든 잘쓰면 약이 되고 못쓰면 독이 되는 법이 것지만..