처리주관 인덱스에 속한 컬럼 중에서 중복 사용된 IN조건이 있다고 해서 모든 IN이 항상 결합처리 실행계획으로 분리되는 것이 아니다. 인덱스를 구성하고 있는 컬럼의 개수와 주어진 값이 상수값이냐, 서브쿼리냐에 따라 차이가 있다.
SQL1:
SELECT *
FROM TAB1
WHERE 지역='서울'
AND 구분 IN('3','1')
AND 발생일자 IN('19980601','19980602')
SQL2:
SELECT *
FROM TAB1
WHERE 지역 IN('서울','부산')
AND 구분 IN('3','1')
AND 발생일자 IN('19980601','19980602')
SELECT *
FROM TAB1
WHERE 지역='서울'
AND 구분 IN('3','1')
AND 발생일자 IN('19980601','19980602')
AND 부서코드 LIKE '211%'
중복 사용된 IN조건에 서브쿼리가 포함되어 있으면 상수값들만 있을 때와는 큰 차이가 있다. 특히 서브쿼리의 IN 조건과 상수값의 IN 조건의 순서에 따라 실행계획은 크게 달라진다.
SELECT ..................
FROM TAB2
WHERE 제품 = 'KH2200'
AND 부서코드 IN ( SELECT 부서코드
FROM 부서
WHERE 부서코드 LIKE '21%' )
and 매출구분 IN ( '1', '5', '7' ) ;
지금까지 설명한 중복된 IN의 실행계획의 수립상태를 다음 도표를 통해 정리
(가정 : 여기서 사용된 인덱스는 COL1, COL2, COL3순으로 구성)
서브쿼리가 포함되어 있는 경우 힌트로서도 실행계획 분리가 나타나지 않는 경우가 있다. 이 경우 모든 점을 서브쿼리에서 생성하여 메인쿼리에게 제공자 역할을 하도록하여 원하는 목표를 이룰 수 있다.
SELECT ..................
FROM TAB2
WHERE 제품 = 'KH2200'
and 부서코드 LIKE :DEPT||'%'
and 매출구분 IN ( '1', '5' )
and 매출일자 BETWEEN :S_DATE and :E_DATE ;
이러한 문제를 해결하기 위해 아래와 같이 쿼리를 변경 시킨다.
SELECT ..................
FROM TAB2
WHERE ( 제품, 부서코드, 매출구분, 매출일자 ) IN
( SELECT 'KH2200',
부서코드,
substr(NO,2,1),
YMD
FROM 부서 x, COPY_T y, YMD_DUAL z
WHERE 부서코드 LIKE :DEPT||'%'
and y.NO IN ( '1', '5' )
and YMD BETWEEN :S_DATE and :E_DATE )
IN 서브쿼리가 공급자 역활을 했음에도 불구하고, 메인쿼리가 이 결과값을 이용하는 인덱스가 없는 경우, 오히려 부하를 크게 증가시킨다.
즉, 서브쿼리가 제공한 결과값이 '주류' 역활을 하지 못하고, '비주류' 역활을 하게 됨으로써, 제공받은 각각의 값들마다 메인쿼리가 중복해서 수행되는 현상이 발생한다.
SELECT *
FROM ORD_ITEM_T
WHERE 주문일자 BETWEEN '19980501' AND '19980510'
and 제품 코드 IN (SELECT 제품코드
FROM PRODUCT
WHERE 제품명 LIKE 'SM%')
and 금액 > 1000000 ;
이 SQL의 실행계획을 확인해 보자.
Rows Execution Plan
------- ---------------------------------------------------
530 NESTED LOOPS
530 TABLE ACCESS (BY ROWID) OF 'PRODUCT'
531 INDEX (RANGE SCAN) OF 'PRODUCT_NAME_IDX(NON-UNIQUE)'
260 TABLE ACCESS (BY ROWID) OF 'ORD_ITEM_T'
56800 INDEX (RANGE SCAN) OF 'ORD_ITEM_IDX(NON-UNIQUE)'
이러한 문제를 해결하기 위해 다음과 같은 방법을 사용해 보자.
SELECT *
FROM ORD_ITEM_T
WHERE ( 주문일자, 제품코드 ) IN
( SELECT YMD, 제품코드
FROM YMD_DUAL x, PRODUCT y
WHERE 주문일자 BETWEEN '19980501' AND '19980510'
AND 제품명 LIKE 'SM%' )
AND 금액 > 1000000 ;
다음의 SQL은 공급자 역활을 위한 서브쿼리가 확인자 역활을 하게 된 경우다.
SELECT 종목코드, count(*), sum(계약금액)/1000
FROM 계약내역
WHERE 종목코드 IN ( SELECT 종목코드FROM 종목 )
AND 계약일 = to_char(sysdate,'yyyymmdd')
--------------------------------------------
SELECT STATEMENT
SORT(GROUP BY)
NESTED LOOPS
TABLE ACCESS(FULL)OF '계약내역'
INDEX(UNIQUE SCAN) OF '종목_PK' (UNIQUE)
(1) 서브쿼리의 조건에 확실한 선처리 조건을 부여하는 방법
SELECT 종목코드, count(*), sum(계약금액)/1000
FROM 계약내역
WHERE 종목코드 IN ( SELECT 종목코드 FROM 종목 WHERE 종목코드 > ' ' )
and 계약일 = to_char(sysdate,'yyyymmdd')
------------------------------------------------
SELECT STATEMENT
SORT(GROUP BY)
NESTED LOOPS
INDEX(RANGE SCAN) OF '종목_PK'(UNIQUE)
TABLE ACCESS (BY ROWID) OF '계약내역'
INDEX(RANGE SCAN) OF 'INDEX1' (NON-UNIQUE)
(2) 서브쿼리에 있는 SELECT-LIST 컬럼을 가공시키는 방법
이 방법은 IN의 서브쿼리에 사용된 SELECT-LIST인 '종목코드'를 강제로 가공시켜, 서브쿼리가 나중에 수행될 때 사용할 연결고리를 못쓰게 함으로써 옵티마이져가 서브쿼리를 먼저 수행하게 한다. (데이터베이스 버전에 따라 차이 있을 수 있음)
SELECT 종목코드, count(*), sum(계약금액)/1000
FROM 계약내역
WHERE 종목코드 IN ( SELECT 종목코드 || '' FROM 종목 )
AND 계약일 = to_char(sysdate,'yyyymmdd')
* 실행계획 동일
* 실행계획을 보면 계약내역과 종목이 NESTED LOOPS로 풀린다.
NESTED LOOP에서 종목의 종목코드가 가공되면 연결고리 이상이 생기고 이 경우 이상이 생긴쪽이
먼저 엑세스되는 원리를 이용한 것이다.
(3) 서브쿼리를 GROUP BY 시키는 방법
서브쿼리에 메인쿼리의 컬럼을 가지지 않고 그룹함수를 사용했다면 서브쿼리가 먼저 수행할 가능성이 매우 높다. 이와같이 옵티마이져 특성을 이용하여 서브쿼리가 먼저 실행되도록 유도한다.
SELECT 종목코드, count(*), sum(계약금액)/1000
FROM 계약내역
WHERE 종목코드 IN ( SELECT 종목코드 FROM 종목 GROUP BY 종목코드 )
AND 계약일 = to_char(sysdate,'yyyymmdd')
* 실행계획 동일
(4) 힌트(PUSH_SUBQ)를 사용하는 방법
서브쿼리를 먼저 수행시켜 달라는 'PUSH_SUBQ' 힌트를 사용하여 서브쿼리가 먼저 수행되도록 할 수 있다.
SELECT /*+ PUSH_SUBQ */
종목코드, count(*), sum(계약금액)/1000
FROM 계약내역
WHERE 종목코드 IN ( SELECT 종목코드
FROM 종목 )
AND 계약일 = to_char(sysdate, 'yyyymmdd')
* 실행계획 동일
조건을 만족하는 첫번째 로우를 찾으면 더 이상 처리하지 않고, 멈추도록 하기 위해서 EXISTS나 ROWNUM을 사용한다. 그러나 처리주관 범위가 IN이나 OR일 때, 즉 결합처리 실행계획이 수립된 경우 ROWNUM을 사용하면 처리를 멈추기 못하고 전체 범위를 모두 처리한다.
(인덱스가 '종목코드 + 계약일자' 로 구성)
이러한 문제를 피할갈 수 있는 방법은 다음 SQL의 결합처리 실행계획을 수립하지 않도록 IN조건을 BETWEEN 으로 대치시킨 것이다.