- 바인드 변수를 사용하면 최초 수행할 때 최적화를 거친 실행계획을 캐시에 적재하고,
실행시점에는 그것을 그대로 가져와 값을 다르게 바인딩하면서 반복 재사용하게 됨
이는 변수를 바인딩하는 시점이(최적화 시점보다 나중인) 실행시점이라는 사실에 주목할 필요가
있으며, 이를 해석하면 바인드 변수를 사용함으로 인해 최적의 실행계획을 만드는 것은 한계가
있다는 것을 알 수 있다.
다르게 해석한다면 바인드 변수를 사용함으로 인해 항상 고정적인 PLAN을 보장받을 수 있으며
Literal을 사용함으로 인해 실행계획이 유동적으로 변경되는것을 막는 혜택 또한 볼 수 있다.
-- 1. 테이블 생성
DROP TABLE T;
CREATE TABLE T AS
SELECT *
FROM ALL_OBJECTS
;
-- 2. 테이블 UPDATE
UPDATE T
SET OBJECT_ID = ROWNUM
;
COMMIT;
-- 3. 인덱스 생성
CREATE UNIQUE INDEX T_U1 ON T(OBJECT_ID);
-- 4. 통계정보 생성
BEGIN
DBMS_STATS.GATHER_TABLE_STATS(USER,
'T',
CASCADE => TRUE);
END;
/
-- 5. 통계정보 확인
@STAT
COLUMN_NAME DATA_TYPE DATA_LEN Null DISTINCT_KEY DENSITY NUM_NULLS NUM_BUCKETS SAMPLE_SIZE LAST Analyzed
------------------------------ ---------- -------- ---- -------------- ---------- -------------- ----------- -------------- -------------------
CREATED DATE 7 N 28,849 .000034663 0 1 52,908 2009-12-01 21:39:13
DATA_OBJECT_ID NUMBER 22 Y 231,041 4.3282E-06 640,896 1 231,079 2009-12-01 21:39:13
GENERATED VARCHAR2 1 Y 2 .5 0 1 5,340 2009-12-01 21:39:13
LAST_DDL_TIME DATE 7 N 29,401 .000034012 0 1 52,908 2009-12-01 21:39:13
OBJECT_ID NUMBER 22 N 871,975 1.1468E-06 0 1 5,340 2009-12-01 21:39:13
OBJECT_NAME VARCHAR2 30 N 275,980 3.6235E-06 0 1 871,975 2009-12-01 21:39:13
OBJECT_TYPE VARCHAR2 19 Y 22 .045454545 0 1 5,340 2009-12-01 21:39:13
OWNER VARCHAR2 30 N 197 .005076142 0 1 5,340 2009-12-01 21:39:13
SECONDARY VARCHAR2 1 Y 2 .5 0 1 5,340 2009-12-01 21:39:13
STATUS VARCHAR2 7 Y 1 1 0 1 5,340 2009-12-01 21:39:13
SUBOBJECT_NAME VARCHAR2 30 Y 3,958 .000252653 776,936 1 5,874 2009-12-01 21:39:13
TEMPORARY VARCHAR2 1 Y 2 .5 0 1 5,340 2009-12-01 21:39:13
TIMESTAMP VARCHAR2 19 Y 30,744 .000032527 0 1 52,908 2009-12-01 21:39:13
NUMBER_ROWS CLUSTERING BLEVEL AvgLfBlperKEY INITRAN Init/Next/ FLst/
OWNER.INDEX_NAME TABLESPACE DISTINCT_KEY FACTOR LEAF_BLOKCS AvgDtBlperKEY DEG LOGGIN MAXTRAN MIN/MAX FGrp LAS
---------------------------------------- ---------------------- --------------- -------------- ------------ ------------- ---- ------ ------- ------------- ----- ---
USER.T_U1 USER_DATA 871,975 12,325 [2] 1 1 YES 2/255 128k/128k / 200
871,975 1,820 1 1/unlimit
OWNER.INDEX_NAME Uniq Type Status Drop COLUMN LIST
---------------------------------------- ---- ------ ------ ---- ----------------------------------------------------------------------------------------------------
USER.T_U1 UNIQ NORMAL VALID NO OBJECT_ID
-- 6. Trace 실행
@mysess
MY_SESSION_INFO
--------------------------------------------------
Sid, Serial# : 9964, 50
OS Process : 2452:4440 (CPID), 627066 (SPID)
ALTER SESSION SET EVENTS '10046 trace name context forever, level 4';
VAR VAL NUMBER
EXEC :VAL := 1000;
SELECT OBJECT_NAME
FROM T
WHERE OBJECT_ID = :VAL
;
OBJECT_NAME
-----------------
V_$ENQUEUE_STAT
ALTER SESSION SET SQL_TRACE=FALSE;
-- 7. TRC 파일 직접 확인
-- 바인드 변수를 실제 실행하는 시점이 CURSOR를 PARSING 하고 난 이후임을 알 수 있음
PARSING IN CURSOR #3 len=52 dep=0 uid=44 oct=3 lid=44 tim=9138570012406 hv=4215685038 ad='6148aef0'
SELECT OBJECT_NAME
FROM T
WHERE OBJECT_ID = :VAL
END OF STMT
PARSE #3:c=0,e=602,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=1,tim=9138570012400
BINDS #3:
kkscoacd
Bind#0
oacdty=02 mxl=22(22) mxlc=00 mal=00 scl=00 pre=00
oacflg=03 fl2=1000000 frm=00 csi=00 siz=24 off=0
kxsbbbfp=11049ac98 bln=22 avl=02 flg=05
value=1000
EXEC #3:c=0,e=3008,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=1,tim=9138570015551
FETCH #3:c=0,e=139,p=0,cr=4,cu=0,mis=0,r=1,dep=0,og=1,tim=9138570015811
FETCH #3:c=0,e=1,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=0,tim=9138570017748
1. 바인드 변수 Peeking
- 바인드 변수의 부작용을 극복하려고 오라클은 9i부터 바인드 변수 Peeking 기능을 도입함
- 'Peeking'이란 바인드 변수를 가진 CURSOR를 최초 수행할 때 해당 바인드 변수의 Literal 값을 한번 보도
실행계획을 작성한 뒤 해당 CURSOR가 재사용될 때 이전에 살펴본 Literal 값을 참조하여 실행계획을 작성하는 방식 - 이 방식의 문제는 분포도가 심하게 차이가 나는 경우(예를들어 A=90%, B=9%, C=1%) 최초 수행시 값이 A라면
FULL SCAN이 좋겠지만, 그 이후 C가 들어옴에도 불구하고 최초 수행됐던 A를 참조하여 계속 FULL SCAN을 하는 단점이 있음 - 더욱 큰 문제는 10g부터 DBMS_STAT 기본 설정이 히스토그램을 생성할지 여부를 오라클이 판단하게 되어
이에 대한 통제를 하지 않게 되면 9i보다 더욱 많은 히스토그램이 생성되게 되고 이로 인해 바인드 Peeking에 의한
폐해가 더욱 커지게 된다. - 이로 인해 대부분의 사이트에서는 이 Peeking 기능을 FALSE로 설정하고 사용함
SELECT A.KSPPINM NAME,
B.KSPPSTVL VALUE,
B.KSPPSTDF DEF_YN,
A.KSPPDESC DESCRIPTION
FROM X$KSPPI A,
X$KSPPSV B
WHERE A.INDX = B.INDX
AND LOWER(A.KSPPINM) LIKE '%' || TRIM(LOWER('_optim_peek_user_binds')) || '%'
ORDER BY 1
;
NAME VALUE DEF_YN DESCRIPTION
---------------------- ----- ------ ------------------------------
_optim_peek_user_binds FALSE FALSE enable peeking of user binds
2. Adaptive Cursor Sharing
3. 입력 값에 따라 SQL 분리
- 바인드 변수는 Literal 값처럼 Cardinarity를 옵티마이저가 정확히 구할 수 없으므로 값의 분포도에 따라
최적의 실행계획을 만들기가 힘듬 - 이를 해결하기 위해 값의 분포도를 미리 알고 있다면 해당 값에 따라 SQL을 분기하여 최적의 실행계획을 유도할 수 있음
- 아래는 분포도가 큰 '서울시', '경기도' 값은 FULL SCAN, 분포도가 작은 다른 지역은 INDEX RANGE SCAN을 UNION ALL을
사용하여 유도한 내용
SELECT /*+ FULL(A) */
*
FROM 아파트매물 A
WHERE :CITY IN ('서울시', '경기도')
UNION ALL
SELECT /*+ INDEX(A IDX01) */
*
FROM 아파트매물 B
WHERE :CITY NOT IN ('서울시', '경기도')
AND 도시 = :CITY;
- 하지만 모든 분기마다 UNION ALL을 사용하게 되면 Parsing 시간이 더 커져서 수행속도 및 리소스를 증가시킬 수 있으므로
IF ELSE 분기로 하나의 SQL만 액세스하도록 유도
IF :CITY IN ('서울시', '경기도') THEN
SELECT /*+ FULL(A) */
*
FROM 아파트매물 A;
ELSE
SELECT /*+ INDEX(A IDX01) */
*
FROM 아파트매물 B
WHERE 도시 = :CITY;
END IF;
4. 예외적으로, Literal 상수값 사용
- Literal 값보다 바인드 변수를 사용하는 것이 대부분 CURSOR 재활용 측면에서 좋긴 하지만 일부 측면에서는
Literal로 사용하는 것이 더 좋은 경우가 있다.
이는 특정 컬럼의 갑 종류가 얼마 없는 경우인데, 이 경우에는 하드파싱의 부하가 미미하게 되며 옵티마이저가
Literal 값을 미리 알고 있기 때문에 더 좋은 실행계획을 생성할 수 있다. - 그러므로 이를 잘 활용하여 적재적소에 Literal을 적게만 활용하도록 하자
문서에 대하여