Bind Peeking 한계에 따른 Adaptive Cursor Sharing 등장_Wh oracle
1. 124│2013 기술백서 White Paper
Bind Peeking 한계에 따른 Adaptive Cursor Sharing
등장
㈜엑셈 컨설팅본부/DB컨설팅팀 김 철환
Bind Peeking 의 한계
SQL 이 최초 실행되면 3 단계의 과정을 거치게 되는데 Parsing 단계를 거쳐 Execute 하고
Fetch 의 과정을 통해 데이터를 사용자에게 전송하게 되며 Parsing 단계에서 실행계획이 생성
된다. Bind 변수를 사용하는 SQL 에서 실행계획이 생성될 때 실제로 사용 되는 변수 값을 모르
기 때문에 성능에 문제를 발생 시킬 수 있는 실행계획이 세워질 수 있다.
이런 Bind 변수의 단점을 보완하기 위해 10g 에서 Bind Peeking 이라는 기능이 소개되었는데
Bind 변수를 사용하는 SQL 에서 최초 실행 되는 시점의 실제 Bind 값이 무엇이냐에 따라 실행
계획이 결정 되는 것을 말한다. 이렇게 되면 실제 사용되는 Bind 변수의 값을 이용해 실행계획
을 세울 수 있어 보다 성능에 유리한 실행계획을 세울 수 있을 것이라 생각 되지만 Bind
Peeking 또한 한계를 가지고 있다.
최초에 Bind 변수 값에 따라서 실행계획이 고정 되기 때문에 최초 값이 SQL 의 성능을 결정하게
되어 Table Full Scan 으로 실행 계획이 생성 되었다면 이 SQL 은 새로운 실행계획이 생성되기
전까지 Table Full Scan 이 발생하게 된다. 이러한 문제로 이 기능은 전혀 사용하지 못하는 기능
이 되어 버렸다. 결국 Bind 변수의 단점을 보완 하기 위해 Bind Peeking 이라는 기능이 소개되
었지만 여전히 이 기능도 한계를 가지고 있다.
진화된 Cursor Sharing 필요
이러한 Bind Peeking 기능의 한계를 보완하기 위해서 Oracle 11g 에서는 Adaptive Cursor
Sharing (적응적 커서 공유) 이라는 기능이 소개 되었는데 Adaptive Cursor Sharing 뜻은 상
황에 맞게 유연하게 Cursor Share 하겠다는 의미이다. 기능을 사용하게 되면 여러 개의 실행계
2. Part 1 ORACLE │125
획을 관리 할 수 있는데 최초 입력된 값에 의해 실행계획이 결정 되더라고 이후에 새로운 Bind
변수 값이 사용되면 이에 맞게 적절한 실행계획을 선택하여 SQL 을 실행 하게 된다. 즉 Bind
Peeking 의 한계를 보완한 것이다.
Cursor Sharing 동작 원리
이 기능이 동작하려면 기본적으로 조건 Column 에 히스토그램이 생성되어 있어야 한다. 그 상
태에서 Bind 변수가 포함된 쿼리를 실행하였을 때, 옵티마이저가 히스토그램 분포도에 따라 실
행 계획이 크게 달라질 것이라 판단한다면, 해당 커서를 Bind Sensitive 커서라는 상태로 두게
된다. 이러한 Bind Sensitive 커서에 해당하는 구문이 재차 실행되었을 때에, 특정 변수 값에서
성능이 크게 저하되었다고 판단되면 해당 커서를 Bind Aware 커서 상태로 바꾼다. 이 상태가
되면, 종전의 성능 저하된다 판단된 커서에 대해서는 기존의 실행 계획을 사용하지 않고, child
커서를 생성하고 새로운 실행 계획을 생성하여 저장하게 되고 새로운 실행 계획을 생성해낸 뒤
에는 선택도가 비슷한 것으로 판단되는 Cursor 는 같은 실행 계획을 사용하게 된다.
Adaptive Cursor Sharing 설정 방법
Adaptive Cursor Sharing 기능을 설정하는 방법을 알아 보자.
Parameter 을 이용한 Adaptive Cursor Sharing 설정
Adaptive Cursor Sharing 기능을 사용할 지의 여부를 지정하며 기본값은 True 이다.
Alter [ System |Session ] Set "_optimizer_adaptive_cursor_sharing" = TRUE;
통계정보 수집을 하여 Histogram을 사용 할 수 있어야 함.
Adaptive Cursor Sharing 기능을 사용하기 위해서는 입력 되는 변수들의 분포도를 알아야 하
는데 그 정보는 Histogram 을 통해 알 수 있다.
3. 126│2013 기술백서 White Paper
EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, TABLE , ESTIMATE_PERCENT=>100,
METHOD_OPT=>'FOR COLUMNS SIZE ');
Adaptive Cursor Sharing 고려사항
Adaptive Cursor Sharing 을 사용하지 않을 경우에는 쿼리가 실행될 때마다 캐시에 저장된 실
행 계획을 실행하게 되지만 Adaptive Cursor Sharing 을 사용하게 되면 Bind 변수의 값이 변경
되었을 때 실행 계획을 재 작성 해야 하는지의 여부를 판단 하게 된다. 이 기능을 모든 SQL 이
사용하게 된다면 시스템에 많은 과부하가 발생하게 된다. 따라서 모든 SQL 에 대해서 이 기능을
사용하기 보다는 Parameter 을 False 적용한 후에 이 기능이 꼭 필요한 SQL 에서 세션 단위로
Parameter 을 True 로 변경하여 사용하는 것이 좋다.
Adaptive Cursor Sharing 활용 방안
조회하는 SQL 에서 조회 조건의 Column 의 중복 값의 분포가 고르지 못하여 실행 계획을 분리
해야 하는 경우가 있는데 같은 SQL 에서 입력 값에 따라 Index Scan 이나 Full Table Scan 을
배타적으로 실행해야 성능에 유리한 경우가 있다. 이러한 경우 Adaptive Cursor Sharing 기능
을 사용하게 되면 입력 값에 따라 최적에 실행 계획을 생성 하여 SQL 의 성능을 향상 시킬 수 있
다.
테스트 데이터 생성
ACCT_NO 값이 각각 10 만 건과 10 건으로 데이터를 생성해 보았다.
CREATE TABLE TB_DPS_TRSC_BASE AS
SELECT 1 ACCT_NO, 'BANK' CUST_NO , ROUND(DBMS_RANDOM.VALUE(10,100)) AMT
FROM DUAL
CONNECT BY LEVEL <= 100000;
INSERT INTO TB_DPS_TRSC_BASE SELECT 99,'NAME',ROUND(DBMS_RANDOM.VALUE(10,100) FROM
ALL_OBJECTS WHERE ROWNUM <= 10;
COMMIT;
인덱스 생성
CREATE INDEX TB_DPS_TRSC_BASE_1IX ON TB_DPS_TRSC_BASE (ACCT_NO) ;
Parameter 설정
4. Part 1 ORACLE │127
Alter Session Set "_optimizer_adaptive_cursor_sharing" = TRUE ;
통계정보를 생성하지 않고 10 만 건의 경우와 10 건의 경우를 테스트
SELECT TABLE_NAME,COLUMN_NAME,HISTOGRAM FROM DBA_TAB_COLS WHERE
TABLE_NAME='TB_DPS_TRSC_BASE';
TABLE_NAME COLUMN_NAME HISTOGRAM
------------------------- ------------------------- ---------------
TB_DPS_TRSC_BASE ACCT_NO NONE
TB_DPS_TRSC_BASE CUST_NO NONE
TB_DPS_TRSC_BASE AMT NONE
-- 테스트를 위해 Shared pool 을 Flush 한다.
ALTER SYSTEM FLUSH SHARED_POOL;
EXEC :A1 := 99;
SELECT *
FROM TB_DPS_TRSC_BASE A
WHERE ACCT_NO = :A1
----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.01 | 3 |
| 1 | TABLE ACCESS BY INDEX ROWID| TB_DPS_TRSC_BASE | 1 | 33467 | 10 |00:00:00.01 | 3 |
|* 2 | INDEX RANGE SCAN | IND_01 | 1 | 33467 | 10 |00:00:00.01 | 2 |
----------------------------------------------------------------------------------------------------------
-- SQL 의 실행계획이 변경될 수 없어 "N" 값으로 표시 됨.
SELECT CHILD_NUMBER, EXECUTIONS, BUFFER_GETS, IS_BIND_SENSITIVE, IS_BIND_AWARE
FROM V$SQL
WHERE SQL_ID = '%48barbrzj2a30';
CHILD_NUMBER EXECUTIONS BUFFER_GETS IS_BIND_SENSITIVE IS_BIND_AWARE
------------ ---------- ----------- ----------------- -------------
0 1 3 N N
EXEC :A1 := 1;
SELECT *
FROM TB_DPS_TRSC_BASE A
WHERE ACCT_NO = :A1
5. 128│2013 기술백서 White Paper
----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 100K |00:00:00.01 | 34454 |
| 1 | TABLE ACCESS BY INDEX ROWID| TB_DPS_TRSC_BASE | 1 | 33467 | 100K |00:00:00.01 | 34454 |
|* 2 | INDEX RANGE SCAN | IND_01 | 1 | 33467 | 100K |00:00:00.01 | 32 |
----------------------------------------------------------------------------------------------------------
SELECT CHILD_NUMBER, EXECUTIONS, BUFFER_GETS, IS_BIND_SENSITIVE, IS_BIND_AWARE
FROM V$SQL
WHERE SQL_ID = '%48barbrzj2a30';
CHILD_NUMBER EXECUTIONS BUFFER_GETS IS_BIND_SENSITIVE IS_BIND_AWARE
------------ ---------- ----------- ----------------- -------------
0 1 34454 N N
EXEC :A1 := 1;
SELECT *
FROM TB_DPS_TRSC_BASE A
WHERE ACCT_NO = :A1
----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 100K |00:00:00.01 | 34454 |
| 1 | TABLE ACCESS BY INDEX ROWID| TB_DPS_TRSC_BASE | 1 | 33467 | 100K |00:00:00.01 | 34454 |
|* 2 | INDEX RANGE SCAN | IND_01 | 1 | 33467 | 100K |00:00:00.01 | 32 |
----------------------------------------------------------------------------------------------------------
SELECT CHILD_NUMBER, EXECUTIONS, BUFFER_GETS, IS_BIND_SENSITIVE, IS_BIND_AWARE
FROM V$SQL
WHERE SQL_ID = '%48barbrzj2a30';
CHILD_NUMBER EXECUTIONS BUFFER_GETS IS_BIND_SENSITIVE IS_BIND_AWARE
------------ ---------- ----------- ----------------- -------------
0 1 34454 N N
통계정보가 존재 하지 않기 때문에 Adaptive Cursor Sharing 기능을 사용 할 수 없어 동일한 실행
계획이 수립되어 실행 된다.(Index Rang Scan )
통계정보 생성하여 10 만 건의 경우와 10 건의 경우를 테스트
EXEC DBMS_STATS.GATHER_TABLE_STATS('SYSTEM','TB_DPS_TRSC_BASE', ESTIMATE_PERCENT=>100,
METHOD_OPT=>'FOR COLUMNS SIZE 2 ACCT_NO');
SELECT TABLE_NAME,COLUMN_NAME,HISTOGRAM FROM DBA_TAB_COLS WHERE
TABLE_NAME='TB_DPS_TRSC_BASE';
6. Part 1 ORACLE │129
TABLE_NAME COLUMN_NAME HISTOGRAM
------------------------- ------------------------- ---------------
TB_DPS_TRSC_BASE ACCT_NO HEIGHT BALANCED
TB_DPS_TRSC_BASE CUST_NO NONE
TB_DPS_TRSC_BASE AMT NONE
ALTER SYSTEM FLUSH SHARED_POOL;
EXEC :A1 := 99;
SELECT *
FROM TB_DPS_TRSC_BASE A
WHERE ACCT_NO = :A1
----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.01 | 3 |
| 1 | TABLE ACCESS BY INDEX ROWID| TB_DPS_TRSC_BASE | 1 | 33467 | 10 |00:00:00.01 | 3 |
|* 2 | INDEX RANGE SCAN | IND_01 | 1 | 33467 | 10 |00:00:00.01 | 2 |
----------------------------------------------------------------------------------------------------------
-- SQL 의 실행계획이 변경될 수 있다면 IS_BIND_SENSITIVE 값이 "Y" 로 나타난다.(Bind 변수 값이
있으면 "Y" 로 표시됨.)
SELECT CHILD_NUMBER, EXECUTIONS, BUFFER_GETS, IS_BIND_SENSITIVE, IS_BIND_AWARE
FROM V$SQL
WHERE SQL_ID = '%48barbrzj2a30';
CHILD_NUMBER EXECUTIONS BUFFER_GETS IS_BIND_SENSITIVE IS_BIND_AWARE
------------ ---------- ----------- ----------------- -------------
0 1 3 Y N
EXEC :A1 := 1;
SELECT *
FROM TB_DPS_TRSC_BASE A
WHERE ACCT_NO = :A1
----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 100K |00:00:00.01 | 34454 |
| 1 | TABLE ACCESS BY INDEX ROWID| TB_DPS_TRSC_BASE | 1 | 33467 | 100K |00:00:00.01 | 34454 |
|* 2 | INDEX RANGE SCAN | IND_01 | 1 | 33467 | 100K |00:00:00.01 | 32 |
----------------------------------------------------------------------------------------------------------
SELECT CHILD_NUMBER, EXECUTIONS, BUFFER_GETS, IS_BIND_SENSITIVE, IS_BIND_AWARE
FROM V$SQL
WHERE SQL_ID = '%48barbrzj2a30';
7. 130│2013 기술백서 White Paper
CHILD_NUMBER EXECUTIONS BUFFER_GETS IS_BIND_SENSITIVE IS_BIND_AWARE
------------ ---------- ----------- ----------------- -------------
0 1 34454 Y N
EXEC :A1 := 1;
SELECT *
FROM TB_DPS_TRSC_BASE A
WHERE ACCT_NO = :A1
------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 100K|00:00:00.06 | 6887 |
|* 1 | TABLE ACCESS FULL| TB_DPS_TRSC_BASE | 1 | 33467 | 100K|00:00:00.06 | 6887 |
------------------------------------------------------------------------------------------------
--Bind 변수 값에 따라 실행계획이 변경되어야 하는지 결정하고 실행계획이 변경되어야 한다면
IS_BIND_AWARE 값이 "Y" 로 표시됨.
SELECT CHILD_NUMBER, EXECUTIONS, BUFFER_GETS, IS_BIND_SENSITIVE, IS_BIND_AWARE
FROM V$SQL
WHERE SQL_ID = '%48barbrzj2a30';
CHILD_NUMBER EXECUTIONS BUFFER_GETS IS_BIND_SENSITIVE IS_BIND_AWARE
------------ ---------- ----------- ----------------- -------------
0 2 34454 Y N
1 1 6687 Y Y
EXEC :A1 := 99;
SELECT *
FROM TB_DPS_TRSC_BASE A
WHERE ACCT_NO = :A1
----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.01 | 3 |
| 1 | TABLE ACCESS BY INDEX ROWID| TB_DPS_TRSC_BASE | 1 | 33467 | 10 |00:00:00.01 | 3 |
|* 2 | INDEX RANGE SCAN | IND_01 | 1 | 33467 | 10 |00:00:00.01 | 2 |
----------------------------------------------------------------------------------------------------------
SELECT CHILD_NUMBER, EXECUTIONS, BUFFER_GETS, IS_BIND_SENSITIVE, IS_BIND_AWARE
FROM V$SQL
WHERE SQL_ID = '%48barbrzj2a30';
CHILD_NUMBER EXECUTIONS BUFFER_GETS IS_B_SENS IS_B_AWAR IS_SHAR
------------ ---------- ----------- --------- --------- ----------
0 2 3454 Y N N
1 1 6687 Y Y Y
8. Part 1 ORACLE │131
2 2 32 Y Y Y
결론
Bind 변수를 사용하는 SQL 에서 처음 사용되는 변수 값으로 실행계획을 세우게 되어 그 실행계
획이 SQL 의 성능에 문제를 발생 시킬 수 있어 Bind Peeking 기능은 사용하지 못하는 기능이
되어 버렸다. 하지만 이 기능의 개선으로 Adaptive Cursor Sharing 기능을 사용하게 되면 이
문제를 해결 할 수 있다. 하지만 그 기능의 장점과 단점을 잘 알고 사용해야만 시스템을 안정적
으로 사용할 수 있을 것이다. 또한 운영하고 있는 시스템을 Adaptive Cursor Sharing 기능을
사용할 수 있는 부분이 있을 것이다. 이 기능을 활용해서 SQL 의 성능을 개선 시킨다면 더욱 안
정화되고 최적화된 시스템으로 발전하게 될 것이다.