세션 커서를 캐싱하면 SGA의 공유 커서를 빠르게 찾아서 커서를 오픈할 수 있다. 하지만 세션 커서 캐시에 있는 SQL을 수행하더라도 공유 커서 힙을 Pin하고 실행에 필요한
메모리 공간을 PGA에 할당하는 등의 작업은 반복하게 된다. 이 과정마저 생략하고 빠르게 SQL을 수행하는 방법이 있는데 이를 '애플리케이션 커서 캐싱(Application Cursor Caching)' 이라고 한다.
개발 언어마다 구현방법이 다르므로 이 기능을 활용하려면 API를 잘살펴봐야한다.
for(;;){
EXEC ORACLE OPTION (HOLD_CURSOR=YES);
EXEC ORACLE OPTION (RELEASE_CURSOR=NO);
EXEC SQL INSERT ...... ; // SQL 수행
EXEC ORACLE OPTION (RELEASE_CURSOR=YES);
}
call count cpu elapsed disk query current rows
--------- -------- -------- ---------- -------- --------- ------------ --------
Parse 1 0.00 0.00 0 0 0 0
Execute 5000 0.18 0.14 0 0 0 0
Fetch 5000 0.17 0.23 0 10000 0 5000
--------- -------- -------- ---------- -------- --------- ------------ --------
total 10001 0.35 0.37 0 10000 0 5000
Misses in library cache during parse: 1
일반적으로는 Execute Call 횟수만큼 Parse Call이 반복되지만 애플리케이션 커서 캐싱 기능을 이용하면 공유 커서를 Pin한 채 반복 수행하므로 Parse Call이 최초 한번만 발생하고 이후로는 발생하지 않는다.
public static void noBinding(Connection conn, int count)throws Exception{
PreparedStatement stmt;
ResultSet rs;
for(int i = 1; i <= count; i++){
stmt = conn.prepareStatement(
"SELECT /* no_binding */" + i + ", " + i + ", 'test', a.* " +
"FROM emp a WHERE a.ename LIKE 'W%'");
rs = stmt.executeQuery();
rs.close();
stmt.close();
}
}
바인드 변수를 사용하지 않았으므로 매번 하드 파싱을 반복 수행
public static void noCaching(Connection conn, int count)throws Exception{
PreparedStatement stmt;
ResultSet rs;
for(int i = 1; i <= count; i++){
stmt = conn.prepareStatement(
"SELECT /* no_caching */ ?, ?, ?, a.* " +
"FROM emp a WHERE a.ename LIKE 'W%'");
stmt.setInt(1, i);
stmt.setInt(2, i);
stmt.setString(3, "test");
rs = stmt.executeQuery();
rs.close();
stmt.close();
}
}
Parse Call이 Execute Call 횟수만큼 발생하게 된다. 하지만 하드파싱은 전혀 발생하지 않거나 한번쯤 발생한다.
public static void cursorHolding(Connection conn, int count)throws Exception{
// PreparedStatement를 루프문 바깥에 선언.
PreparedStatement stmt = conn.prepareStatement(
"SELECT /* cursor_holding */ ?, ?, ?, a.* " +
"FROM emp a WHERE a.ename LIKE 'W%'");
ResultSet rs;
for(int i = 1; i <= count; i++)
{
stmt.setInt(1, i);
stmt.setInt(2, i);
stmt.setString(3, "test");
rs = stmt.executeQuery();
rs.close();
}
// 루프를 빠져 나왔을 때 커서를 닫는다.
stmt.close();
}
Parse Call이 한번만 발생
public static void cursorCaching(Connection conn, int count)throws Exception{
// 캐시 사이즈를 1로 지정
((OracleConnection)conn).setStatementCacheSize(1);
// 묵시적 캐싱 기능을 활성화
((OracleConnection)conn).setImplicitCachingEnabled(true);
for(int i = 1; i <= count; i++){
PreparedStatement stmt = conn.prepareStatement(
"SELECT /* implicit_caching */ ?, ?, ?, a.* " +
"FROM emp a WHERE a.ename LIKE 'W%'");
stmt.setInt(1, i);
stmt.setInt(2, i);
stmt.setString(3, "test");
ResultSet rs = stmt.executeQuery();
rs.close();
// 커서를 닫지만 내부적으로는 닫히지 않은 채 캐시에 보관
stmt.close();
}
}
Parse Call이 한번만 발생
SQL> alter session set session_cached_cursors = 100;
SQL> create table t ( x number);
SQL> alter system flush shared_pool;
SQL> declare
i number;
begin
for i in 1..100
loop
execute immediate 'insert into t values(' || mod(i, 10) || ')';
end loop;
commit;
end;
SQL> select count(distinct sql_text) sql_cnt
2 , sum(parse_calls) parse_calls
3 , sum(executions) executions
4 from v$sql
5 where sql_text like 'insert into t values%';
SQL_CNT PARSE_CALLS EXECUTIONS
--------- ----------- ----------
10 100 100
0부터 9까지의 값을 입력하는 10개 SQL을 불연속적으로 실행했더니 Parse Call이 SQL 수행횟수만큼 발생했다.
SQL> declare
i number;
begin
for i in 1..100
loop
execute immediate 'insert into t values(' || ceil(i/10) || ')';
end loop;
commit;
end;
SQL> select count(distinct sql_text) sql_cnt
, sum(parse_calls) parse_calls
, sum(executions) executions
from v$sql
where sql_text like 'insert into t values%';
SQL_CNT PARSE_CALLS EXECUTIONS
--------- ----------- ----------
10 10 100
1부터 10까지의 값을 입력하는 10개 SQL을 연속적으로 업력했더니 Parse Call 이 SQL 개수만큼만 발생했다 .
SQL> alter session set session_cached_cursors = 0;
SQL> alter system flush shared_pool;
SQL> declare
i number;
begin
for i in 1..100
loop
execute immediate 'insert into t values(' || ceil(i/10) || ')';
end loop;
commit;
end;
SQL> select count(distinct sql_text) sql_cnt
, sum(parse_calls) parse_calls
, sum(executions) executions
from v$sql
where sql_text like 'insert into t values%';
SQL_CNT PARSE_CALLS EXECUTIONS
--------- ----------- ----------
10 100 100
1부터 10까지의 값을 입력하는 10개 SQL을 연속적으로 입력했지만 세션 커서 캐싱 기능을 비활성화시켰더니 Parse call이 SQL 수행횟수만큼 발생했다 .