Skip to content
튜닝 연습인덱스 스캔 효율과 테이블 액세스
ACC-041

월 판매번호 목록이 지난주부터 477블록에서 1,757블록을 읽는다

중급SELECT유통116 / 700

상황 ​

유통 본사의 「월 판매번호 목록」은 회계 연동용으로 한 달 치 판매번호와 판매일자를 내려준다. 지난주 외부 점검 보고서에 이런 줄이 있었다. "버거원에서 INDEX_JOIN으로 테이블 액세스를 없애 블록을 4분의 1로 줄였다. 같은 모양의 SQL에 적용을 권고한다." 조민우가 권고대로 힌트를 붙였는데, 이 SQL은 오히려 무거워졌다. 아래는 2023년 6월을 골랐을 때 힌트를 붙인 뒤와 붙이기 전이다.

테이블과 인덱스 ​

  • CF10 판매: 500,000행 · 4,583블록(블록당 약 109행) · 판매 날짜순으로 적재 · 2023년 6월 20,953건(교재 03화 실측) · 컬럼은 모두 NOT NULL
  • 인덱스 CF10_PK (SLE_NO): BLEVEL 2 · 리프 약 1,680블록 · IX_CF10_SLE_DT (SLE_DT): BLEVEL 2 · 리프 1,393블록 · CLUSTERING_FACTOR 9,921(교재 03화 실측) · 통계 최신
  • 참고: 버거원 ORD_RANDOM의 날짜 인덱스는 CLUSTERING_FACTOR가 999,958이다(행 수와 같다)

원본 SQL ​

sql
SELECT /*+ INDEX_JOIN(a IX_CF10_SLE_DT CF10_PK) */
       a.SLE_NO, a.SLE_DT
  FROM CF10 a
 WHERE a.SLE_DT BETWEEN :v_from AND :v_to

실행계획 학습용 예시 · 6월 건수와 IX_CF10_SLE_DT의 CF는 교재 03화 실측 ​

xplan
[힌트를 붙인 뒤]
---------------------------------------------------------------------------------------------------------------
| Id  | Operation               | Name             | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
---------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |                  |      1 |        |  20953 |00:00:00.09 |    1757 |          |
|*  1 |  FILTER                 |                  |      1 |        |  20953 |00:00:00.09 |    1757 |          |
|*  2 |   VIEW                  | index$_join$_001 |      1 |   2094 |  20953 |00:00:00.08 |    1757 |          |
|*  3 |    HASH JOIN            |                  |      1 |        |  20953 |00:00:00.08 |    1757 | 2316K (0)|
|*  4 |     INDEX RANGE SCAN    | IX_CF10_SLE_DT   |      1 |   2094 |  20953 |00:00:00.01 |      61 |          |
|   5 |     INDEX FAST FULL SCAN| CF10_PK          |      1 |   2094 |    500K|00:00:00.05 |    1696 |          |
---------------------------------------------------------------------------------------------------------------
   1 - filter(:V_FROM<=:V_TO)
   2 - filter(("SLE_DT">=:V_FROM AND "SLE_DT"<=:V_TO))
   3 - access(ROWID=ROWID)
   4 - access("SLE_DT">=:V_FROM AND "SLE_DT"<=:V_TO)

[힌트를 붙이기 전]
---------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name           | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |                |      1 |        |  20953 |00:00:00.03 |     477 |
|*  1 |  FILTER                              |                |      1 |        |  20953 |00:00:00.03 |     477 |
|   2 |   TABLE ACCESS BY INDEX ROWID BATCHED| CF10           |      1 |   2094 |  20953 |00:00:00.03 |     477 |
|*  3 |    INDEX RANGE SCAN                  | IX_CF10_SLE_DT |      1 |   2094 |  20953 |00:00:00.01 |      61 |
---------------------------------------------------------------------------------------------------------
   1 - filter(:V_FROM<=:V_TO)
   3 - access("SLE_DT">=:V_FROM AND "SLE_DT"<=:V_TO)

문제 ​

  1. 힌트를 붙이기 전과 뒤의 실행계획을 줄 단위로 비교해, 테이블에 가지 않게 됐는데도 Buffers가 477에서 1,757로 늘어난 이유를 설명하시오.
  2. 버거원의 SQL에서는 같은 처방이 왜 효과가 있었는지 클러스터링 팩터로 설명하고, 인덱스 조인이 테이블 액세스보다 유리해지는 조건을 정리하시오.
  3. SQL을 고치시오. 블록을 더 줄일 방법이 있다면 그 대가와 함께 제안하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자