05. Fetch Call 최소화
(1) 부분범위처리 원리
create table t (
x NUMBER not null
, y NUMBER not null ) ;
insert into t
select *
from (
select rownum x, rownum y
from dual
connect by level <= 5000000
)
order by dbms_random.value
;
alter table t add
constraint t_pk primary key (x);
alter system flush buffer_cache;
*set arraysize 5*
set timing on
select /*+ index(t t_pk) */ x, y
from t
where x > 0
and y <= 6 ;
X Y
---------- ----------
1 1
2 2
3 3
4 4
5 5 ? 엔터를 치자마자 여기까지 출력하고 멈춤
6 6 ? 33초 경과한 후에 이 라인을 출력하고 수행 종료
예> 1억 건짜리 BIG_TABLE을 쿼리하는 데 1초가 채 걸리지 않는 이유
예> ArraySize를 5로 설정
(2) OLTP 환경에서 부분범위처리에 의한 성능개선 원리
<p.376 그림 참고>
*set arraysize 5*
select /*+ index(t t_pk) */ x, y
from t
where x > 0
and y <= 6 ;
X Y
---------- ----------
1 1
2 2
3 3
4 4
5 5 ? 엔터를 치자마자 여기까지 출력하고 멈춤
6 6 ? 33초 경과한 후에 이 라인을 출력하고 수행 종료
select /*+ index(t t_pk) */ x, y
from t
where x > 0
and y <= 1 ;
X Y
---------- ----------
1 1 ? 307초 후에 이라인을 출력하고 곧바로 수행 종료
1. select /*+ index(t t_pk) */ * from t where x > 0 and mod(y, 50) = 0
2. select /*+ index(t t_pk) */ * from t where x > 0 and mod(y, 50000) = 0
One-Row Fetch
SQL*Plus에서 쿼리를 수행하면 첫 번째 Fetch에서는 항상 한 건만 요청(One-Row Fetch)하고, 두 번째부터 ArraySize 만큼을 요청한다.
'x > 0 and y <= 5' 조건일 때, 결과전체수는 총 5건이므로 첫 번째 Fetch에서 1건을 우선 가져왔지만 두 번째 Fetch에서 4건 밖에
찾을 수 없어 끝까지 대기하느라고 오래 걸리는 것이다.
첫 번째 Fetch에서 1건을 빠르게 리턴 받지만 곧바로 출력하지 않은 이유는, Fetch 해 오는 방식과는 무관하게 Array 버퍼로는 5개를
할당하기 때문이며 클라이언트는 이 버퍼가 다 채워져야 출력을 시작한다.
'x > 0 and y <-1' 조건으로 수행할 때는 첫 번째 Fetch Call에서 대기할 것 같지만 실장은 두 번째 Fetch Call에서 대기한다.
첫 번째 Fetch에서 이미 1건을 가져왔지만 클라이언트 쪽 Array 버퍼 크기가 5이므로 나머지 4개를 마저 채우려고 두 번째 Fetch Call을 날리고 기다리는 것이다.
'x > 0 and y <= 6' 조건일 경우는 첫 번째 Fetch에서는 1건만 가져오고, 두 번째 Fetch에서 5건을 가져와 클라이언트 측 Array 버퍼를 채운다,
5개가 채워졌으므로 일단 화면에 출력하고, 남은 1건 외에 4건을 더 가져와 Array 버퍼를 채우려고 세 번째 Fetch를 수행하는데, 이 때 지연이 발생하게 된다.
- 쿼리 툴마다 다른 방식으로 데이터를 Fetch 한다.
- Array 단위로 멈추지 않고 모든 데이터를 다 Fetch하는 쿼리 툴도 있다.
- 어떤 클라이언트 툴을 사용하든 서버 측에서는 항상 Array 단위로 전송한다.
(3) ArraySize 조정에 의한 Fetch Call 감소 및 블로 I/O 감소 효과
(4) 프로그램 언어에서 Array 단위 Fetch 기능 활용
*# Implicit Cursor FOR Loop*
declare
l_object_name big_table.object_name%type;
begin
for item in ( select object_name from big_table where rownum <= 1000 )
loop
l_object_name := item.object_name;
dbms_output.put_line(l_object_name);
end loop;
end;
/
*# Explicit Cursor FOR Loop*
declare
l_object_name big_table.object_name%type;
cursor c is select object_name from big_table where rownum <= 1000;
begin
for item in c
loop
l_object_name := item.object_name;
dbms_output.put_line(l_object_name);
end loop;
end;
/
declare
cursor c is
select object_name
from big_table where rownum <= 1000;
l_object_name big_table.object_name%type;
begin
open c;
loop
fetch c into l_object_name;
exit when c%notfound;
dbms_output.put_line(l_object_name);
end loop;
close c;
end;
/
PreparedStatment stmt = conn.prepareStatment(sql);
*Stmt.setFetchSize(100);*
ResultSet rs = stmt.executeQuery()
// *rs.setFetchSize(100); -- ResultSet에서 조정할 수도 있다.*