_대문 | 방명록 | 최근글 | 홈피소개 | 주인놈 |
FrontPage › 커서를언제사용해야할까
|
|
[edit]
1 개요 #데이터베이스 관련 커뮤니티에 보면 가끔 커서에 관련된 질문이나 글들이 올라오고는 한다. 대부분은 커서를 사용하지 말라고 못박아 논 경우가 대부분이다. 그 이유는 커서를 사용함으로 인해 안 좋은 일을 겪었기 때문이라고 생각한다. 그러나 필자는 커서를 무조건 사용하지 말라고 못박지는 않는다. 다만 커서를 선언해 줄 때 선언되는 SQL문의 결과집합의 양을 최대한 줄인 후 커서를 사용하라는 것이다. 물론 필자도 최대한 커서를 사용하지 말라는데 한 표를 던진다. 커서가 성능상 안 좋다고 하는 이유는 선언된 집합의 양과 행 단위 처리에 문제가 있다. 일단 데이터베이스에서 행 단위 처리를 하게 된다면 성능은 현격히 떨어진다. 그러나 어쩔 수 없이 커서를 사용해야 하는 경우도 있다. 그런 경우는 동적으로 결과가 변하는 것일 때이다. 다음과 같은 예이다.
[edit]
2 예제 #use tempdb go if exists (select * from sysobjects where type = 'U' and name = 'tableA') drop table tableA create table tableA(group_id int , [rand] int) go insert into tableA values(11, 2) insert into tableA values(12, 4) insert into tableA values(13, 1) go if exists (select * from sysobjects where type = 'U' and name = 'tableB') drop table tableB create table tableB(group_id int , id varchar(10)) go insert into tableB values(11, '개똥이') insert into tableB values(11, '말똥이') insert into tableB values(11, '소똥이') insert into tableB values(11, '땡칠이') insert into tableB values(12, '옥동자') insert into tableB values(12, '아무개') insert into tableB values(12, '홍길동') insert into tableB values(12, '고길동') insert into tableB values(12, '김길동') insert into tableB values(12, '박길동') insert into tableB values(13, '개뿔') insert into tableB values(13, '소뿔') go select * from tableA /* group_id rand ----------- ----------- 11 2 12 4 13 1 */ select * from tableB /* group_id id ----------- ---------- 11 개똥이 11 말똥이 11 소똥이 11 땡칠이 12 옥동자 12 아무개 12 홍길동 12 고길동 12 김길동 12 박길동 13 개뿔 13 소뿔 */Group_id 가 11인 것은 랜덤으로 2개를 가져오고, Group_id가 12인 것은 랜덤으로 4개를 가져오는 형태이다. 랜덤한 것을 보장하기 위해서 아래와 같은 뷰를 생성하였다. create view v_tableB as select top 100 percent group_id, id from tableB order by NewID() --다소 부하가 있다. --SQL Server 2005 버전에서는 TableSample 키워드를 사용하면 된다. 그리고 한번에 처리하기 위해서 동적 쿼리를 작성하였다. 실제로 커서를 돌면서 SQL문이 만들어지는 과정이다.
DECLARE @group_id int , @rand int , @count int , @cursor_count int , @sql varchar(1000) SET @cursor_count = 1 BEGIN DECLARE table_cursor CURSOR FOR SELECT group_id, rand FROM tableA SELECT @count = COUNT(*) FROM tableA OPEN table_cursor FETCH NEXT FROM table_cursor INTO @group_id, @rand SET @sql = 'SELECT TOP ' + CAST(@rand AS VARCHAR) + ' group_id, id FROM v_tableB ' + 'WHERE group_id = ' + CAST(@group_id AS VARCHAR) + ' UNION ALL ' WHILE @@FETCH_STATUS = 0 BEGIN FETCH NEXT FROM table_cursor INTO @group_id, @rand IF(@count <> @cursor_count) BEGIN SET @sql = @sql + 'SELECT TOP ' + CAST(@rand AS VARCHAR) + ' group_id, id FROM v_tableB ' + 'WHERE group_id = ' + CAST(@group_id AS VARCHAR) + ' ' Print @count Print @cursor_count END SET @cursor_count = @cursor_count + 1 IF(@count > @cursor_count) SET @sql = @sql + 'UNION ALL ' END CLOSE table_cursor DEALLOCATE table_cursor PRINT @sql EXEC(@sql) END /* group_id id ----------- ---------- 11 개똥이 11 말똥이 12 옥동자 12 아무개 12 홍길동 12 고길동 13 개뿔 */ 실제로 @sql을 프린트 해보면 다음과 같이 쿼리문이 동적으로 만들어진 것을 볼 수 있다.
SELECT TOP 2 group_id, id FROM v_tableB WHERE group_id = 11 UNION ALL SELECT TOP 4 group_id, id FROM v_tableB WHERE group_id = 12 UNION ALL SELECT TOP 1 group_id, id FROM v_tableB WHERE group_id = 13 실제로 v_tableB라는 뷰를 3번이나 접근한다. 즉, 뷰의 내용대로 tableB는 NewID()로 정렬이 되어 랜덤한 값을 얻기는 하지만 group_id가 많은 값이라면 성능은 점차 나빠질 것이다. 또한 WHERE 조건이 group_id에 의해 결정되므로 실제로는 group_id에 반드시 인덱스가 잡혀 있어야 하겠다.
|
진실을 말하는 자가 되어라. 외톨박이가 되는 것을 겁내지 말아라. 친구들은 끝내는 돌아올 것이다. (로댕) |