본문 바로가기

카테고리 없음

Note 001 # 인덱스 기본활용



 Note 001_01

    # Range Scan이 불가능한 경우

다음과 같이 단일 인덱스 컬럼을 가공하면 Index Range Scan은 불가능하다.

단, Index Full Scan 또는 Fast Index Full Scan은 가능하다. 

  • 인덱스 컬럼에 함수를 사용한 경우
  • 인덱스 컬럼에 결합연산을 사용한 경우
  • 인덱스 컬럼에 사칙연산을 사용한 경우
  • 묵시적 형변환이 발생한 경우
  • HAVING 절에서 필터링할 경우
  • Not-Nullable 인덱스의 어떤 컬럼에 IS NULL 연산자를 사용한 경우
인덱스가 Nullable 하다는 것은
인덱스의 모든 컬럼이 Nullable 하다는 것을 의미한다.


 Note 001_02

    # 인덱스 스캔이 불가능한 경우

오라클에서는 모든 컬럼값이 Null인 튜플은 저장하지 않는다.
그러한 튜플을 검사하려면 어쩔 수 없이 Table Full Scan이 발생한다.
  • Nullable 인덱스의 어떤 컬럼에 IS NULL 연산자를 사용한 경우


 Note 001_03

    # 스캔이 아예 일어나지 않는 경우

다음 상황에서는 스캔이 아예 일어나지 않는다.
굳이 스캔하지 않아도 결과집합이 없음을 예측할 수 있기 때문이다.
  • Not-Nullable 인덱스의 모든 컬럼에 IS NULL 연산자를 사용한 경우


 Plus Note 001_01

    # Range Scan이 불가능한 경우

    # 묵시적 형변환이 발생한 경우

12c-R2 벤더에서 묵시적 형변환으로 인한 비효율적인 실행계획을 재현할 수 없었다.

옵티마이저가 스스로 묵시적 형변환을 처리하도록 개선 된 것 같다. 


다음 쿼리를 살펴보자.


쿼리에서 1을 명시적으로 char 타입으로 캐스팅 했음에도 불구하고

옵티마이저가 이를 무시하고 number 타입으로 캐스팅 한 것에 주목하길 바란다. 


-- 인덱스 X_LINEITEM_02 : [L_PARTKEY + L_SUPPKEY]
SELECT COUNT(*)
FROM   LINEITEM
WHERE  L_PARTKEY = to_char(1);


----------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ----------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 5 | 3 (0)| 00:00:01 | | 1 | SORT AGGREGATE | | 1 | 5 | | | |* 2 | INDEX RANGE SCAN| X_LINEITEM_02 | 30 | 150 | 3 (0)| 00:00:01 | ----------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("L_PARTKEY"=1)


만약 이전 버전의 벤더였다면 다음과 비슷한 실행계획이 출력될 것 이다.


---------------------------------------------------------------------------------------
| Id  | Operation             | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |               |     1 |     5 |  4646   (2)| 00:00:01 |
|   1 |  SORT AGGREGATE       |               |     1 |     5 |            |          |
|*  2 |   INDEX FAST FULL SCAN| X_LINEITEM_02 |    30 |   150 |  4646   (2)| 00:00:01 |
---------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access(TO_CHAR("L_PARTKEY")=TO_CHAR(1))


DECODE 함수에서 사용된 묵시적 형변환도 같은 결과를 보였다.

12c부터는 묵시적 형변환으로 인한 비효율은 걱정하지 않아도 될 것이다.



 Plus Note 001_02

    # Range Scan이 불가능한 경우

    # HAVING 절에서 필터링할 경우

SELECT 문장에서 각 절의 실행순서를 간략하게 설명하면

FROM ㅡ WHERE ㅡ GROUP BY ㅡ HAVING ㅡ ORDER BY 순서이다.


따라서 WHERE 절이 아닌 HAVING 절에서 필터링을 수행하면

인덱스를 사용하지 못한 채 GROUP BY를 수행하는 꼴이 된다.


다음 계획에서 필터링 조건을 수행하는 필터 계획이

GROUP BY 상단에 위치하는 것에 주목하기 바란다.


-- 인덱스 X_LINEITEM_02 : [L_PARTKEY + L_SUPPKEY]
SELECT L_PARTKEY, COUNT(*)
FROM   LINEITEM
GROUP BY L_PARTKEY
HAVING   L_PARTKEY < 50
ORDER BY L_PARTKEY;


---------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 50 | 250 | 4927 (7)| 00:00:01 | |* 1 | FILTER | | | | | | | 2 | SORT GROUP BY | | 50 | 250 | 4927 (7)| 00:00:01 | | 3 | INDEX FAST FULL SCAN| X_LINEITEM_02 | 6001K| 28M| 4631 (1)| 00:00:01 | ---------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("L_PARTKEY"<50)


GROUP BY를 수행했기 때문에 L_PARTKEY<50 조건을 필터링할 때에도

인덱스를 사용하지 못한것을 알 수 있다.


반면 WHERE 절에서 필터링을 처리한다면

Range Scan 후 GROUP BY를 수행하는 실행계획이 수립될 것 이다.


다음 계획에서 필터링 조건을 수행하는 액세스 계획이

GROUP BY 하단에 위치하는 것에 주목하기 바란다.


-- 인덱스 X_LINEITEM_02 : [L_PARTKEY + L_SUPPKEY]
SELECT L_PARTKEY, COUNT(*)
FROM   LINEITEM
WHERE  L_PARTKEY < 50
GROUP BY L_PARTKEY
ORDER BY L_PARTKEY;


-------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 49 | 245 | 7 (0)| 00:00:01 | | 1 | SORT GROUP BY NOSORT| | 49 | 245 | 7 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | X_LINEITEM_02 | 1470 | 7350 | 7 (0)| 00:00:01 | -------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("L_PARTKEY"<50)




 Plus Note 001_03

    # 스캔이 아예 일어나지 않는 경우

    # Not-Nullable 인덱스의 모든 컬럼에 IS NULL 연산자를 사용한 경우

이러한 경우에는 굳이 실제로 수행하지 않아도

결과집합이 없음을 쉽게 예측할 수 있다.


이런 경우에는 오라클이 자동적으로 NULL IS NOT NULL 조건절을 추가하여

불필요한 스캔계획이 실제로 수행하지 않도록 한다.


아래 실행계획에서 INDEX FAST FULL SCAN이 발생했지만,

오라클이 추가한 FILTER 계획에 의하여 전체비용이 0이 된것에 주목하기 바란다.


-- 인덱스 X_LINEITEM_02 : [L_PARTKEY + L_SUPPKEY]
SELECT COUNT(*)
FROM   LINEITEM
WHERE  L_PARTKEY IS NULL
AND    L_SUPPKEY IS NULL;


---------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 9 | 0 (0)| | | 1 | SORT AGGREGATE | | 1 | 9 | | | |* 2 | FILTER | | | | | | | 3 | INDEX FAST FULL SCAN| X_LINEITEM_02 | 6001K| 51M| 4631 (1)| 00:00:01 | ---------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter(NULL IS NOT NULL AND NULL IS NOT NULL)