다음 sp 를 봅시다.

create proc dbo.up_test
as

set nocount on
set transaction isolation level read uncommitted

declare @v int

select @v = isnull(col1,0) from test where col2 = '1'

select @v


test 테이블의 데이터는 이렇게 되어 있습니다.

[test]
col1    col2
2       '2'

데이터가 이렇게 되어 있으니까 select문은 아무것도 리턴하지 않겠지요. 아마도 이런 상황을 대비해서 isnull() 을 썼을 겁니다. 그러면 과연 예상대로 0 이 나올까요??

...

돌려보시면 안그렇습니다. isnull() 에도 불구하고 NULL 이 리턴됩니다. isnull 이 동작하려면 실제로 NULL이라는 값이 테이블에 존재하고, 그것을 읽어와야지 됩니다. 그런데 이 경우, 아예 읽어온 것이 아무것도 없죠. 따라서 declare 다음에 set @v = 0 을 해줘야 합니다. 초기화를 한 후에 sp 를 돌려봅시다.

어떻습니까? 0 이 나오지요??

사실 어떤 언어로 개발을 하던 변수 초기화는 매우 중요한 문제입니다. T-SQL이라고 예외는 아니지요. sp가 알 수 없는 데이터를 리턴한다던가, 예상치 않은 방식으로 동작한다면 저런 경우가 없는지 한번쯤 의심해 볼 일입니다. ;-)


--------추가---------
위의 예문에서 초기화를 하지 않았을 경우 조회하는 결과값이 항상 하나만 리턴된다면

select @v = isnull(min(col1),0) from test where col2 = '1'

이렇게 해도 됩니다.

Posted by maceo

03 20, 2006 11:59 03 20, 2006 11:59
, , ,
Response
No Trackback , No Comment
RSS :
http://merritt.co.kr/tt/rss/response/39

coalesce 함수의 특이한 동작

박노철
maceo.park@gmail.com
http://merritt.co.kr

SQL Server 를 보면 isnull 과 coalesce 가 있다. isnull 은 다 아는거고 coalesce 는 사람들이 잘 모르는데, 넘겨받은 n개의 파라미터중 null 이 아닌 첫번째 값을 리턴하는 함수다. (사족인데, isnull 은 ANSI 표준이 아니고 coalesce 는 ANSI 표준이다. Joe Celko 책에는 isnull 의 자리에 coalesce 을 사용하고 있다. )

우찌됐던간에, coalesce 는 함수 자체의 특성상 다음과 같은 상황에 유용하게 쓰일 수 있다.

1. 카테고리별로 설정된 어떤 값을 읽어온다. 만약 그게 없으면...
2. 상품별로 설정된 어떤 값을 읽어온다. 그게 없으면...
3. 판매자별로 설정된 어떤 값을 읽는다. 그래도 없으면
4. 디폴트값을 리턴한다.

어떤 제약사항이 우선순위를 가지고 적용되는 경우라 할 수 있겠다. coalesce 를 써서 위의 로직을 대충 적어보면 다음과 같다.

select
coalesce(select val from cate where cate_id=@id,
select val from prd where prd_id=@id,
select val from seller where seller_id=@id,
10)

(cate_id, prd_id, seller_id 는 PK라 가정)


물론 isnull 로도 구현할 수 있다. isnull 을 중첩해서 쓰면 되니까.

select
isnull(select top 1 val from cate where cate_id=@id,
isnull(select top 1 val from prd where prd_id=@iid,
isnull(select top 1 val from seller where seller_id=@id,10)
)
)


하지만 코드는 coalesce 쪽이 더 깔끔해보인다.

그리고 이제사 본론인데, coalesce 가 실제 성능상 더 좋은 효율을 보인다. 만약 cate 에서 NULL 이 아닌 val 값이 리턴되었다면 "상식적으로" prd 나 seller 테이블을 액세스할 필요가 없다. 그런데 isnull 은 그런 상식과 반대로 행동한다. 일단 무조건 cate, prd, seller 를 모두 액세스한 다음, isnull 연산을 시작한다. 반면 coalesce 는 첫번째 파라미터가 NULL 이 아닌 값이 리턴이 되면 그 다음 파라미터들은 evaluation 하지 않고 처리를 끝낸다. 따라서 cate 에서 NULL 아닌 값이 리턴되었다면 prd, seller 테이블은 액세스하지 않는다.

믿기지 않겠지만 사실이다. ^^;

확인해보기 위해서는 set statistics io on 을 해서 나오는 scan count 를 보면 된다. isnull 을 쓰면 세개 테이블 모두 scan 카운트가 1이지만 coalesce 는 cate 만 scan count 가 1이고 나머지 테이블의 scan count 는 0 이다.

coalesce 를 쓸 때 주의할 점이 하나 있다. 위의 사례는 coalesce 안에 서브쿼리를 사용한 것이다. SQL서버 2000, 2005 모두 coalesce 안에 서브쿼리가 들어가면 실행계획이 엉망이 되는 버그? 가 있다. 아마 위의 쿼리를 그냥 실행하면 cate, prd, seller 테이블을 두번씩 액세스하는 실행계획이 세워질 것이다. (참조 : http://www.aspfaq.com/show.asp?id=2532 )아까전에는 scan count 가 1 이라고 했지만, 사실은 잘못된 실행계획때문에 scan count가 2이다. 이를 방지하기 위해서는 쿼리를 다음과 같이 만들어야 한다.

select
coalesce(select top 1 min(val) from cate where cate_id=@id,
select top 1 min(val) from prd where prd_id=@id,
select top 1 min(val) from seller where seller_id=@id,
10)


cate_id, prd_id, seller_id 가 PK 이지만 일부러 min 을 씌워서 값이 없을때도 확실하게 NULL 을 리턴하도록 했고, top 1 을 해서 한개의 값만 가져오도록 했다 .이렇게 하면 정확하게 우리가 원하느대로 scan count 는 1이 나오면서 불필요한 테이블을 액세스하지 않는다. coalesce 와 서브쿼리의 실행계획에 대한 문제, 기타 isnull 과 coalesce 에 관해서는 다음 문서를 참조한다.

http://www.examnotes.net/archive79-2002-8-55509.html 에서 Umachandar Jayachandran 의 리플라이

http://toponewithties.blogspot.com/2004/08/differences-between-coalesce-and.html

여기를 시작으로 블로그 트랙백을 따라가다 보면 엄청많은 문서들이 있다.

Posted by maceo

03 20, 2006 09:40 03 20, 2006 09:40
, , ,
Response
No Trackback , No Comment
RSS :
http://merritt.co.kr/tt/rss/response/36

박노철
maceo.park@gmail.com
http://merritt.co.kr

다음과 같은 테이블을 생각해보자.

create table #test_tbl
(
   col1 tinyint default 600
)


600이면 tinyint 의 범위를 벗어난다. 이런 테이블을 만들 수 있을까......???

...

테스트해봐라. 된다 -_-;;;;; 2000, 2005 모두 다 된다.
그럼 진짜로 값이 뭔가 들어갈까???

insert into #test_tbl default values


이건 에러를 낸다.
Msg 220, Level 16, State 2, Line 1
Arithmetic overflow error for data type tinyint, value = 600.
The statement has been terminated.


테이블 만들어질때뿐 아니라. alter table add 로 컬럼 추가할 때도 저런 컬럼을
추가할 수 있다. DB의 버그일까, 원래 RDB의 사상에 맞도록 만들어진 것일까?
알 수 없는 노릇이다. 쩝.


Posted by maceo

03 19, 2006 03:06 03 19, 2006 03:06
, ,
Response
3 Trackbacks , No Comment
RSS :
http://merritt.co.kr/tt/rss/response/37

FK 컬럼에는 무조건 인덱스를!

박노철
maceo.park@gmail.com
http://merritt.co.kr

다음 테이블을 생각해보자.
create table parent
(
col1 int primary key
)

create table child
(
col1 int references parent (col1)
)


child.col1 은 parent.col1 이 non-identifying relation 으로 내려온 것이다. 이제 child 테이블에 대해서 insert 를 하나 해보자.

insert child (col1) values (1)


실행계획이 어떻게 나오는지 확인해보자.



뭔가 이상하지 않은가? child 에 insert 하는데 parent 쪽을 CIDX seek 을 한다. 생각해보면 너무 당연하다. FK 가 걸려있으므로 insert 하기 전에 일단 parent 쪽을 먼저 체크해봐야 한다. 또하나 재밌는 점은, 일단 Table Insert 가 이뤄진 후, Left Semi Join 을 해서 Assert 를 한다는 사실이다. 즉, 일단 일 저질러 놓고 Assert 로 체크해보고 실패하면 롤백한다. (관련된 이야기가 Inside SQL Server 2000 에 있는 것으로 기억되는데 가물가물하다...) delete 문도 마찬가지다.

delete from parent where col1 = 1



parent 를 delete 하기 전에 child 에 FK로 내려간 데이터가 없나 찾아보고 있다. 그런데 이 실행계획은 심각한 문제를 가지고 있다. 바로 테이블 스캔을 한다는 사실이다. 자, 그렇다면 여기서 너무도 당연하지만 간과하기 쉬운 결론이 도출된다. FK가 존재하는 테이블에서는 FK로 내려간 컬럼이 맨앞에 나오는 인덱스를 무조건 걸자! 안그러면 뭐하나 delete, insert, update 할 때 마다 Index Scan 또는 Table Scan 이 날 수 있다.

Posted by maceo

03 7, 2006 02:04 03 7, 2006 02:04
, ,
Response
No Trackback , a comment
RSS :
http://merritt.co.kr/tt/rss/response/35

중첩 트랜잭션 처리 가이드

중첩 트랜잭션 처리 가이드


박노철
maceo.park@gmail.com


복잡한 업무를 개발하다 보면 sp 를 분리해야 할 경우가 많습니다. 각각의 sp 가 asp 에서 직호출될 때도 트랜잭션이 보장되어야 하고 sp 를 중첩해서 호출할 때도 하나의 트랜잭션으로 처리되어야 합니다. 의외로 이런 상황이 자주 발생하는데, 단독호출/중첩호출 상황을 한 트랜잭션에 모두 커버할 수 있는 표준적인 sp 작성법은 부재한 실정입니다. 본 문서는 이런 상황을 비교적 깔끔하게 해결할 수 있는 sp 작성법을 알려드리고자 쓰여졌습니다. 간단한 시나리오를 가정해서 설명을 하도록 하겠습니다.

1. dbo.up_inner 를 먼저 개발

A라는 프로젝트에서 up_inner 라는 sp 를 만들었습니다. DB개발 가이드가 배포되기 이전의 일반적인 sp 작성법에 따르면 대체로 다음과 같은 방식으로 만들어집니다.


create proc dbo.up_inner
@cust_no varchar(10)
as

set nocount on
set transaction isolation level read uncommitted

begin tran

select (생략...)

insert (생략...)

if @@error <> 0
begin
rollback tran
return
end

update (생략)

if @@error <> 0
begin
rollback tran
return
end

commit tran



asp 에서 이 sp 를 호출해서 사용하는데 아무런 문제가 없습니다. 트랜잭션 처리도 잘 되어 있고 정상적으로 돌아갑니다.

2. dbo.up_outer 를 개발

dbo.up_inner 를 사용해서 프로젝트를 오픈했습니다. 그런데 운영업무 도중 어떤 요구사항 때문에 dbo.up_outer 를 만들고, 그 안에서 dbo.up_inner 를 call 해야 할 일이 생겼습니다. 당연히 dbo.up_outer 전체에서 한 트랜잭션에 모든 것이 처리되어야 합니다. 일단 다음과 같은 코드가 만들어 질 것을 예상할 수 있습니다.

create proc dbo.up_outer
@cust_no varchar(10)
as

set nocount on
set transaction isolation level read uncommitted

begin tran

insert (생략)

if @@error <> 0
begin
rollback tran
return
end

exec dbo.up_inner @cust_no

delete (생략)

if @@error <> 0
begin
rollback tran
return
end

commit tran


위의 코드를 하나하나 뜯어보겠습니다. 언뜻 보기에는 에러처리가 잘 되어 있는 듯 하지만 여러 문제점이 존재합니다.

exec dbo.up_inner @cust_no

이 부분이 가장 심각한 문제가 있습니다. 호출하는 것 자체는 문제가 아닙니다. 하지만 dbo.up_inner 에서 어떤 에러로 인해 rollback 을 했다고 가정합시다. 그런데 문제는 dbo.up_inner 에서 rollback 을 하면 dbo.up_inner 내부에서 시작한 트랜잭션만 rollback 하는 것이 아니라 자신이 속해있는 transaction context 를 모두 rollback 해버립니다. 즉, dbo.up_outer 에서 시작한 트랜잭션을 rollback 해버리는 것이지요.

이제 어떻게 될까요?

네… dbo.up_outer 의 commit 에서 에러를 냅니다. 만약 delete 문이 실패했다면, rollback 을 하려고 해도 dbo.up_inner 에서 트랜잭션이 이미 rollback 되었기 때문에 에러를 냅니다. 이런 상황은 여러 번 겪어보셨을 줄로 압니다. 그래서 기존의 코드들을 보면 dbo.up_inner 에서 트랜잭션을 빼는 방향으로 수정해서 이런 상황을 해결하고 있습니다. 하지만 아시다시피 dbo.up_inner 에서 트랜잭션을 제거하면 dbo.up_inner 가 단독으로 호출되었을 때 데이터 정합성을 보장받을 수 없죠. 데이터 맞추느라 짚더미에서 바늘찾기를 해보신 분들이라면 데이터 정합성의 중요성을 뼈저리게 느끼고 있으실 겁니다.

이 상황을 깔끔하게 해결하는 방법이 없을까요?

네 있습니다. 모든 sp 를 DB개발 가이드의 에러처리 샘플을 정확하게 지켜서 개발하시면 됩니다. ㅎㅎㅎ 개발 가이드의 에러처리 샘플은 중첩호출 상황을 염두에 두고 만들어진 것입니다. 이제 dbo.up_inner 와 dbo.up_outer 를 에러처리 샘플에 맞춰서 수정해보겠습니다.

[dbo.up_inner]

create proc dbo.up_inner
@cust_no varchar(10)
as

set nocount on
set transaction isolation level read uncommitted

begin tran

select (생략...)

insert (생략...)

if @@error <> 0
begin
goto HANDLE_ERROR -- 에러가 발생하면 공통 에러 처리 레이블로 점프
end

update (생략)

if @@error <> 0
begin
goto HANDLE_ERROR
end

commit tran

if @@nestlevel = 1 – 중첩레벨 1 은 asp 에서 직접호출
select -1 as ret_code
return -- 반드시있어야함! 이게없으면아래쪽의HANDLE_ERROR 까지실행하게됨
else -- 중첩레벨1 아닌 것은 외부sp 에서 중첩호출
return -1


HANDLE_ERROR:
if @@trancount > 0 rollback tran
if @@nestlevel = 1
select -1 as ret_code
else
return -1


볼드체로 된 부분에 주목해 주세요. 에러 처리 로직을 한군데로 몰았고, commit 후나 rollback 후에 @@nestlevel 을 체크해서 최종 결과 코드를 리턴하고 있습니다. @@nestlevel 은 중첩레벨을 나타내는 시스템 함수입니다. @@nestlvel 이 0 이면 asp 에서 호출한 것이므로 select 로 에러코드를 넘겨줍니다. @@nestlevel 이 0 보다 크면 외부 sp 에서 중첩호출한 것이므로 return 문으로 에러코드를 넘겨줍니다. 또는 OUTPUT 변수로 넘겨줄 수도 있겠습니다. (참고로 return 문은 numeric 데이터만 리턴가능합니다. 문자는 리턴할 수 없습니다)

[dbo.up_outer]

create proc dbo.up_outer
@cust_no varchar(10)
as

set nocount on
set transaction isolation level read uncommitted

begin tran

insert (생략)

if @@error <> 0
begin
goto HANDLE_ERROR
end

declare @ret_code int

exec @ret_code = dbo.up_inner @cust_no --dbo.up_inner에서 return하 고 결과코드를 받음

if @@error <> 0 or @ret_code < 0 -- @@error 와@ret_code 값에따라에러처리
begin
goto HANDLE_ERROR
end

delete (생략)

if @@error <> 0
begin
goto HANDLE_ERROR
end

commit tran

if @@nestlevel = 1
select -1 as ret_code
return -- 반드시있어야함! 이게 없으면 아래쪽의 HANDLE_ERROR까지 실행하게 됨
else
return -1

HANDLE_ERROR:
if @@trancount > 0 rollback tran – 항상 @@trancount를 체크해서 rollback
if @@nestlevel = 1
select -1 as ret_code
else
return -1


마찬가지로 볼드체 부분에 주목해주세요. dbo.up_inner 를 호출해서 @ret_code 를 받을 때는 위와 같은 방식으로 코드를 작성합니다. 그리고 exec 를 하자마자 바로 @@error 와 @ret_code 를 체크해서 에러처리를 수행합니다. Exec 후에 set @aaa = @bbb 와 같은 코드가 들어가면 그 순간 @@error = 0 이 되어 에러처리 로직이 동작하지 않게 되므로 주의해주세요.

이제 rollback 하는 부분을 특히 주목해서 봐주세요. HANDLE_ERROR 로 넘어올 때는 이미 dbo.up_inner 에서 전체 트랜잭션을 rollback 한 후가 될 수도 있습니다. 따라서 rollback 하기 전에는 언제나 @@trancount 를 체크해서 rollback 하도록 하면 안전한 코드가 됩니다. 그리고 외부에 결과코드를 넘겨주는 것도 @@nestlevel 을 체크해서 수행합니다.

그런데 여기서 한가지 의문이 생길 수 있습니다. 트랜잭션을 중첩시키고 있는데, 중첩 트랜잭션은 단일 트랜잭션보다 시스템에 더욱 부하를 주는 것이 아닌가 하는 것입니다. 결론부터 말씀드리면 트랜잭션을 중첩시킨다고 해서 특별히 부하가 더 가중되는 것은 아닙니다. Begin tran 후에 또 다시 begin tran 은 하게 되면 단지 내부적으로 @@trancount 가 1 증가할 뿐입니다. 그리고 rollback 을 하게 되면 전체 트랜잭션을 rollback 하고 @@trancount 를 0 으로 만듭니다. 중첩 트랜잭션하에서 commit 은 @@trancount 가 1 이 될 때까지 @@trancount 를 하나 감소시키기만 하다가 @@trancount 가 1 인 상태에서 commit 를 만나면 그때서야 비로소 DB에 실제 commit 를 하게 됩니다. 따라서 위와 같은 방식으로 코드를 작성해도 특별히 DB에 부하가 더 가는 것은 아닙니다. 시스템에 부담을 주는 것은 트랜잭션의 중첩 수준이 아니라 트랜잭션의 길이입니다. 데이터 정합성을 고려해 트랜잭션을 최대한 지키면서 가능한 한 트랜잭션의 길이를 짧게 가져가는 방향으로 고민을 해주시길 부탁드립니다.

Posted by maceo

02 17, 2006 13:45 02 17, 2006 13:45
, ,
Response
No Trackback , No Comment
RSS :
http://merritt.co.kr/tt/rss/response/33

다음 순서대로 읽으면 사고치지 않는 수준의 쿼리는 만들 수 있음

------

1. 정원혁, Microsoft SQL Server 2000 전문가로 가는 길 (초급)
왕초보용. 인덱스만 이해해도 책값은 건진거나 마찬가지

2. 켄 헨더슨, 하성희 역, 실무 예제 중심의 고급 T-SQL 개발자 가이드 (초중급)
중반까지 매우 알찬 내용. 개발시 실수하기 쉬운 점들에 대해서 잘 나옴. 뒷부분의 XML/HTML 부분은 거의 실용성이 없음. 디자인 패턴, 소프트웨어 공학, Agile Methodology 얘기는 수박겉핱기 수준. 안 읽어도 됨

3. 손호성, Deep Inside T-SQL 쿼리 테크닉 (초중급)
인덱스 다시 한번 꼼꼼히 읽고 이해할 것. 중반부 이후의 각종 쿼리 활용부분 잘 이해해두면 좋음. 특히 가로->세로 전환, 세로->가로 전환

4. 주경호, 오라클 실무 튜닝과 SQL 패턴 학습 (중급)
앞부분 오라클 DB 얘기는 모두 건너뛰고 SQL 패턴만 보면 됨. 중급~고급 중간에 위치한 SQL 사례 몇가지 나옴. 꽤 쓸만함.

5. 이화식, 대용량 데이터 베이스 솔루션 1,2 (고급)
영어/일어/중국어가 아닌 "한국어"로 , 이런 책이 동시대에 존재한다는 사실에 저자들에게 아무리 찬사를 퍼부어도 아깝지 않음. 그야말로 최고. 온갖 고급SQL의 성찬. 여기 있는 SQL 능수능란하게 사용할 수 있으면 못할게 없을 것 같음(난 언제나... -_-)

6. Joe Celko, SQL for Smarties - Advanced SQL Programming (중고급)
튼튼한 이론적 기반에 바탕해서 SQL을 만들고 싶은 사람에게 적당. 저자는 ANSI SQL 표준 제정에 참여할 정도의 권위자. ANSI SQL 의 구석구석까지 깊이 있게 다루며 개발자에게 필요한 수준으로 이론적인 설명 + 실제 SQL 이 적절한 비율로 섞여있음. 설명 단순명료. 영어 쉬운 편.

7. 기타
http://www.sqler.pe.kr
http://www.mssql.org

Posted by maceo

01 22, 2006 03:50 01 22, 2006 03:50
Response
No Trackback , No Comment
RSS :
http://merritt.co.kr/tt/rss/response/24

실행계획 내부에 dbid 가 있는 것일까?

오늘 EVENT DB를 EVENT_OLD 로 바꾸고 새로운 db를 EVENT 로 복원한 후, 기존의 EVENT_OLD 를 drop 했다.

그러고 나니 벌어지는 놀라운 현상.

어떤 프로시저는 잘 되는데 어떤 프로시저는

"데이터베이스 ID 9 가 없습니다. 데이터베이스가 비활성화 이거나 전환중에 있습니다"

라는 에러가 떨어진다. 생전 첨보는 에러라 열심히 구글을 해봤더니

1. SQL 서버의 버그로 특정한 join 상황에서 그런 에러를 낼 수 있다.
2. freeproccache 를 해주면 해결되는 경우가 있다.

라는 두가지 의견이 존재. (로그인 관련된 문제가 있는 것은 아니었음. 디폴트 DB도 정확하게 바꿔줬고 ODBC DSN 도 조정해줬음)

같은 sp 가 그런 에러를 내므로 1은 아니고, 2인것을 의심하며 freeproccache 를 실행. 그런데 DB를 못찾는다는 에러와 프로시저 캐쉬가 무슨 상관이 있는지 말도 안된다고 반신반의하면서 캐쉬를 비웠더니만....


문.제.해.결! -_-;;;;;;;


2를 추천한 Tim 어쩌구 하는 SQL Server MVP 왈, 실행계획 내부에는 실행계획이 참조하는 테이블의 dbid 까지 저장하고 있는 것 같다고 함. 그렇다면 말이 된다. 옛날 DB를 drop 해서 없어졌는데, 프로시저 캐쉬에 올라가 있는 실행계획은 옛날 dbid 를 가지고 있을테니까 예전 실행계획으로 실행시키면 에러를 낼 수 밖에 없다.

그런데 정말로 실행계획 내부에는 dbid 도 가지고 있는 것일까??
SQL 서버 소스코드를 보기 전엔 알 수 없는 노릇. -_-;;;;;;

Posted by maceo

01 12, 2006 22:34 01 12, 2006 22:34
Response
No Trackback , No Comment
RSS :
http://merritt.co.kr/tt/rss/response/21

신비의 양쪽 like 검색? -_-

박노철
maceo.park@gmail.com
http://merritt.co.kr

1)
declare @val varchar(10)
set @val = 'TEST'
select * from table1 where col1 like '%' + @val + '%'


2)
select * from table1 where col1 like '%TEST%'

(table1.col1 에 nonclustered index 가 잡혀 있다고 가정)


자, SQL 서버 2000에서 1) 과 2) 의 실행계획은 어떨까? 일반적으로 보면 둘다 index seek 을 하지 못한다고 알려져 있다. 그런데 막상 실제로 보면 안그렇다. 1) 의 경우 '%' + @val + '%' 을 서버가 희한하게 변경한다. 실행계획을 떠보면, col1 >= LikeRangeStart(col1) and col1 < LikeRangeEnd(col1), LikeRangeInfo 이렇게 세개의 Expression 으로 변환한 후, 이것을 Compute Scalar 연산을 한 뒤에 col1 과 inner join 을 한다. 황당하기 그지없다. 2)는 예상한대로 index scan 을 한다.

자 그럼 성능은 어떨까? 1)이 index seek 을 하니까 좋지 않을까? 아니올씨다. 이다. nonclustered index 를 범위검색을 하기 때문에 성능이 결코 좋지 못하다. index scan이야 뭐, 테이블이 커지면 당연히 안좋을 수 밖에 없고.

그럼 이 글은 도대체 왜 적었냐고? SQL서버 옵티마이저가 괴상하게 행동하는게 신기해서 함 적어봤다. ㅎㅎㅎ



@참고로 LikeRangeStart, LikeRangeEnd, LikeRangeInfo 함수는 Books Online 에도 나오지 않으며, 구글을 해봐도 마땅한 정보가 나오지 않는다. MS에서 공개하지 않고 있는 함수의 하나인 것 같다.

Posted by maceo

01 4, 2006 22:05 01 4, 2006 22:05
,
Response
No Trackback , No Comment
RSS :
http://merritt.co.kr/tt/rss/response/18

SQL Linked Server Best Practice

박노철
maceo.park@gmail.com
http://merritt.co.kr

1.
remote server 에 SP 를 만들고 그걸 호출한다. 이게 젤 좋다.

2.
remote 서버에 SP를 만들 권한이 없다면 OPENQUERY 를 사용한다. 이렇게 하면 Linked Server 쪽을 호출할때 메타데이터의 교환이 최소화되어서 매우 빠른 속도로 쿼리가 수행된다.

insert into OPENQUERY(REMOTE, 'select col1, col2 from test.dbo.test_table where 1=0')
values ('aaaa','bbb')

update OPENQUERY(REMOTE, 'select col1, col2 from test.dbo.test_table where col3=1')
set col1 = 'aaaa', col2 = 'bbb'

참고1) OPENQUERY 의 두번째 인자인 SQL은 변수를 받을 수 없다. 만약 변수를 넘겨야 한다면 update 문을 통째로 동적SQL로 만들고 그걸 sp_executesql 로 호출한다. MSDN 에 이 내용을 담은 KB문서가 있는데 못찼겠다.

참고2) update 의 대상이 OPENQUERY 로 읽어오는 remote table 일 경우 update OPENQUERY().. set ... from localtable1 join OPENQUERY() 같은 형태의 SQL은 실행되지 않을 것이다. (긴가민가하다-_-) OLEDB가 뭔가 에러를 냈던 것으로 기억.

참고3) OPENQUERY 와 관련된 문서들

SQL Server 에서 Oracle Table Query 하기

SQL Server 에서 Oracle 에 DDL 수행하기

3.
left join 에는 remote table 을 사용하지 않는다. 예를 들어

select *
from dbo.localtable l
left join remotesvr.test.dbo.test_table r on l.col1 = b.col2

와 같이 left join 에 remote table 을 쓰면 remote table scan 을 해서 몽땅 다 읽어온 다음에 outer join 을 수행할 가능성이 높다. 따라서 실행계획을 반드시 확인해봐야 한다. SQL서버 옵티마이저가 이럴때 보면 좀 멍청한 것 같다. inner loop join 은 괜찮다. 그런데 remote table 과 loop join 을 한다는 것 자체가 network io 를 발생시키는 일이기 때문에 당연히 local table 과 join 하는 것 보다 엄청나게 느릴 수 밖에 없다.

결국 결론은, DB분산을 해도 전혀 상관없는 업무영역끼리 잘 나눠야 한다는 것이다...

Posted by maceo

11 26, 2005 23:34 11 26, 2005 23:34
, ,
Response
No Trackback , No Comment
RSS :
http://merritt.co.kr/tt/rss/response/6

ODBC, OLEDB, ADODB Best Practice

박노철
maceo.park@gmail.com
http://merritt.co.kr


MS 플랫폼에서 대규모의 OLTP 를 하다보니 이것저것 테스트해보다가 알아낸 사실.

. ODBC DSN 을 쓴다는 것은 순수한 의미에서 ODBC 를 쓰는 것이 아니라 Microsoft OLE DB Provider for ODBC driver 를 거쳐서 ODBC 를 사용하는 것이다. 따라서 직접 SQL OLEDB Provider 를 쓰는 것보다 느리다.

. MS플랫폼에서는 두가지의 DB연결 풀링이 존재한다. 하나는 다들 아는 ODBC 커넥션 풀링이고 다른 하나는 OLEDB 세션 풀링이다. 그런데 ODBC DSN 을 써도 사실은 MS OLEDB Provider for ODBC driver 를 사용하는 것이므로 디폴트로 OLEDB 세션풀링을 사용하게 된다. 따라서 ODBC DSN 으로 DB 연결을 해도 ODBC 퍼포먼스 카운터에는 아무것도 잡히지 않는다. ODBC API 를 사용하면 퍼포먼스 카운터가 잡힌다. OLEDB 세션풀링에 관련된 퍼포먼스 카운터는 없는 것으로 알고 있다.

. ODBC DSN 을 쓰면 DB콜 할 때마다 DSN 를 찾기 위해 레지스트리를 뒤지게 된다. OLEDB 를 쓰면 그렇지 않다.

. SQL Server SP3 이후에 ODBC DSN 으로 연결이 이뤄지면 sp_reset_connection 이 호출되지 않는다. 풀링을 통해서 DB연결을 재사용하기는 하지만 재사용되는 연결정보가 깨끗이 정리되지 않는다. 만약 이전 연결에 분리된 트랜잭션이 있었나 temp table 을 지우지 않았다면 이 정보가 그대로 남아있게 된다. OLEDB 를 쓰면 그렇지 않다.

. 만약 DSN=TEST;UID=TEST;PWD=TEST 가 있고 DSN=TeST;UID=TEST;PWD=TEST 이 있다면 커넥션 풀은 두개가 만들어진다. 그리고 서로 다른 프로세스(exe) 에서 DB연결을 시도할 때도 프로세스마다 커넥션 풀이 만들어진다. 즉 같은 프로세스에서 같은 connection string 을 사용해야 동일한 커넥션 풀을 사용하게 된다. connection string 은 대소문자까지 구분한다. 커넥션 풀이 복수개가 생기면 DB서버에 일시에 엄청난 숫자의 커넥션이 몰려버릴 수 있는 가능성을 배제하지 못한다.

. ADODB.Connection 의 ConnectionTimeout 은 커넥션 풀에서 놀고 있는 연결을 얻어올 때까지 기다리는 시간 + DB에 연결을 맺을 때까지 기다리는 시간을 모두 포함한다.

. ADODB.Connection 의 CommandTimeout 과 ADODB.Command 의 CommandTimeout 이 동시에 설정되었다면 ADODB.Command 의 설정치가 적용된다.

. ADODB.Connection 의 Execute 를 쓰지말고 ADODB.Command 를 사용하라. 무조건! 이걸 써야 한다. 스토어드 프로시저와 같이 쓰는게 제일 좋다. SP 실행계획 재사용으로 인한 CPU 사용률 감소, 컴파일LOCK 회피등의 효과가 있다. 하지만 가장 큰 이득은 CPU 가 튀지 않는다는 것이다. 실행계획이 한번 제대로 올라가면 SP 리컴파일이 일어나서 엄한 실행계획을 세우지 않는한 CPU가 매우 안정적인 모습을 보인다. 그리고 갑자기 CPU가 튀면 십중팔구 SP하나가 엄한 실행계획을 타고 있는 것이므로 이넘만 sp_recompile 해주면 다시 조용해진다. 즉, 튀는 놈을 잡아내기가 쉽다.

. 동적SQL은 무조건 피해라. 도저히 안되겠으면 sp_executesql 을 사용하라. 모 사이트에서 DB서버 CPU가 90넘던거를 모든 동적SQL을 sp_executesql 로 바꿔주기만 해도 CPU가 50근처로 떨어지고 CPU사용량도 안정적으로 되었다고 한다.

. 사실 기계나 DB가 딸린다기 보다는 개발을 잘못해서 시스템이 꺽꺽대는 경우가 90% 이상이다. 기계탓, DB탓 하지말고 자신을 탓하는 자세를 가지자-_-;

Posted by maceo

11 26, 2005 22:25 11 26, 2005 22:25
, ,
Response
No Trackback , No Comment
RSS :
http://merritt.co.kr/tt/rss/response/4


블로그 이미지

가늘어도 긴놈이 장땡

- maceo

Archives

Authors

  1. maceo

Calendar

«   9 2010   »
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    

Site Stats

Total hits:
179883
Today:
2
Yesterday:
35