Skip to content
튜닝 연습인덱스 사용 조건
IDX-039

고객번호 열 자리를 끝까지 쳐도 상담 화면은 고객 전체를 찾는 줄 안다

중급SELECT한빛유통39 / 700

상황 ​

고객센터의 「고객 주문 이력」은 상담원이 고객번호를 치면 그 고객의 석 달 치 주문을 최신순으로 보여 준다. 상담원 40명이 하루 9천 번쯤 쓴다. 앞부분만 쳐도 찾게 하려고 화면은 LIKE :v_cus || '%'로 넘기지만, 상담원은 대부분 고객번호 열 자리를 다 친다. 조민우가 (CUS_ID, ORD_DT) 인덱스를 만들었는데도 FULL이라 INDEX 힌트를 붙여 봤다. 인덱스는 탔지만 거의 그대로 느리다. 아래는 'CUS_001234'의 2024-10-01 이후 주문을 찾았을 때다.

테이블과 인덱스 ​

  • T_ORD_BIG: 1,000,000행 · 5,826블록 · CUS_ID VARCHAR2(40) 'CUS_000001' ~ 'CUS_002000'(모두 CUS_로 시작, 고객당 500건) · ORD_DT DATE(전부 자정, 2022-01-01 ~ 2024-12-30)
  • 고객 한 명의 2024-10-01 이후 주문은 42건이고, 테이블 곳곳의 서로 다른 블록에 있다
  • 인덱스 IX_ORD_BIG_CUS_DT (CUS_ID, ORD_DT): BLEVEL 2 · 리프 3,640 · 통계 최신

원본 SQL ​

sql
SELECT o.ORD_SEQ, o.ORD_DT, o.ORD_ST, o.ORD_AMT
  FROM T_ORD_BIG o
 WHERE o.CUS_ID LIKE :v_cus || '%'
   AND o.ORD_DT >= TO_DATE(:v_from, 'YYYYMMDD')
 ORDER BY o.ORD_DT DESC

실행계획 학습용 예시 · 건수는 적재 스크립트 그대로 ​

xplan
[원본]
------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                   |      1 |        |     42 |00:00:00.07 |    5826 |
|   1 |  SORT ORDER BY                      |                   |      1 |  83180 |     42 |00:00:00.07 |    5826 |
|*  2 |   TABLE ACCESS FULL                 | T_ORD_BIG         |      1 |  83180 |     42 |00:00:00.07 |    5826 |
------------------------------------------------------------------------------------------------------------
   2 - filter(("O"."CUS_ID" LIKE :V_CUS||'%' AND "O"."ORD_DT">=TO_DATE(:V_FROM,'YYYYMMDD')))

[조민우의 시도 · /*+ INDEX(o IX_ORD_BIG_CUS_DT) */]
------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                   |      1 |        |     42 |00:00:00.05 |    3685 |
|   1 |  SORT ORDER BY                      |                   |      1 |  83180 |     42 |00:00:00.05 |    3685 |
|   2 |   TABLE ACCESS BY INDEX ROWID BATCHED| T_ORD_BIG        |      1 |  83180 |     42 |00:00:00.05 |    3685 |
|*  3 |    INDEX RANGE SCAN                 | IX_ORD_BIG_CUS_DT |      1 |  83180 |     42 |00:00:00.05 |    3643 |
------------------------------------------------------------------------------------------------------------
   3 - access("O"."CUS_ID" LIKE :V_CUS||'%' AND "O"."ORD_DT">=TO_DATE(:V_FROM,'YYYYMMDD'))
       filter(("O"."CUS_ID" LIKE :V_CUS||'%' AND "O"."ORD_DT">=TO_DATE(:V_FROM,'YYYYMMDD')))

[V$SQL_BIND_CAPTURE]   :V_CUS   VARCHAR2   'CUS_001234'

문제 ​

  1. 고객번호를 정확히 넣었는데도 E-Rows가 83,180이고, 힌트로 인덱스를 태워도 리프 3,643블록을 읽는 이유를 설명하시오.
  2. 상담원이 번호 전체를 칠 때와 앞부분만 칠 때 모두 그 고객의 범위만 읽도록 SQL을 고치시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자