기본적으로 인덱스를 사용한다는건 Index Full Scan이 아니라
Index Range Scan을 한다는 의미다.
인덱스를 정상적으로 사용한다는 것은, 리프블록에서 스캔 시작점을 찾아 거기서 부터 스캔하다가 중간에 멈추는 것을 의미한다. 즉 리프블록을 일부만 스캔하는 Index Range Scan 이다.
인덱스 컬럼을 가공하면 리프블록전체를 스캔해야 한다. 인덱스는 인덱스컬럼을 기준으로 정렬돼있는데 이 값을 가공해버리면 가공된값으로는 인덱스 리프블록이 정렬돼있지 않기때문에 부분만 읽을수 없어서 인덱스리프블록 전체를 스캔해야하는 Index Full Scan방식으로 작동한다.
OR조건으로 검색할 때, 수직적 탐색을 통해 전화번호가 '010' 이거나 고객명이 '홍길동'인 어느 한 시작점을 바로 찾을 수 없다. 따라서 인덱스를 어떤 방식으로 구성해도 Range Scan할 수 없다.
WHERE (전화번호 = :tel_no OR 고객명 = :cust_nm)
이럴때 union all로 플랜을 나눠버리면 전화번호 ,고객명 인덱스 각각에 대해 Index Range Scan을 탈 수 있다.
SELECT *
FROM 고객
WHERE 고객명 = :cust_nm -- 고객명이 선두 컬럼인 인덱스 Range Scan
UNION ALL
SELECT *
FROM 고객
WHERE 전화번호 = :tel_no -- 전화번호가 선두컬럼인 인덱스 Range Scan
AND (고객명 <> :cust_nm OR 고객명 IS NULL)
이렇게 OR 조건식을 SQL옵티마이저가 형식을 바꿔줄수 있는데 이걸 OR Expansion이라고 한다.
use_concat 힌트를 이용해서 OR Expansion을 유도할 수 있다.
SELECT /*+ use_concat*/ *
FROM 고객
WHERE (전화번호 = :tel_no OR 고객명 = :cust_nm)
In조건절도 OR조건을 표현하는 다른 방식일뿐이므로 아래 쿼리도 전화번호가 '010' 이거나 '011' 인 어느 한 지점을 찾는건 불가능하다.
WHERE 전화번호 IN (:tel_no1, :tel_no2)
이럴때, UNION ALL로 플랜을 분리시켜주면 각 브랜치 별로 인덱스 스캔시작점을 찾을수 있고, Range Scan이 가능하다.
SELECT *
FROM 고객
WHERE 전화번호 = :tel_no1
UNION ALL
SELECT *
FROM 고객
WHERE 전화번호 = :tel_no2
그래서 IN조건절에 대해서는 SQL옵티마이저가 In-List Iterator방식을 사용한다. In-LIst개수만큼 Index Range Scan을 반복하는 것이다.