Skip to content
튜닝 연습옵티마이저와 통계
OPT-028

전화번호 뒷자리로 최근 주문 20건을 찾는 상담 화면이 한 번에 7만 블록을 읽는다

중급SELECT한빛유통618 / 700

상황 ​

상담팀의 「전화번호 뒷자리 조회」는 고객이 휴대전화 번호 뒤 네 자리를 불러 주면 그 고객의 최근 주문 20건을 띄운다. 상담원 40명이 하루 약 3,000번 쓴다. 0.2초씩 걸려 통화 중에 화면이 늦게 뜬다고 한다. 개발자는 번호 뒷자리를 MBL_NO LIKE '%' || :tail로 찾게 썼다. 아래 계획은 오늘 아침 첫 조회('1234')로 만들어진 커서다.

테이블과 인덱스 ​

  • M_CUS: 2,000행 · 30블록 · PK_M_CUS (CUS_ID) · MBL_NO '010' + 여덟 자리 · 뒤 네 자리가 같은 고객은 지금 없다
  • T_ORD_BIG: 1,000,000행 · 5,826블록 · 고객당 주문 500건이 날짜마다 흩어져 있다(하루 913건 가운데 한 고객은 두세 건)
  • 인덱스 IX_ORD_BIG_DT (ORD_DT): BLEVEL 2 · 리프 약 2,900 / IX_ORD_BIG_CUS_DT (CUS_ID, ORD_DT): BLEVEL 2 · 리프 약 3,400 / 두 인덱스 모두 CF가 100만에 가깝다
  • 통계 최신 · 히스토그램 없음

원본 SQL ​

sql
SELECT *
  FROM (SELECT o.ORD_SEQ, o.ORD_DT, o.ORD_ST, o.ORD_AMT, c.CUS_NM
          FROM T_ORD_BIG o, M_CUS c
         WHERE c.MBL_NO LIKE '%' || :tail
           AND o.CUS_ID = c.CUS_ID
         ORDER BY o.ORD_DT DESC)
 WHERE ROWNUM <= 20

실행계획 학습용 예시 ​

xplan
-----------------------------------------------------------------------------------------------------
| Id  | Operation                       | Name          | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                |               |      1 |        |     20 |00:00:00.21 |   77171 |
|*  1 |  COUNT STOPKEY                  |               |      1 |        |     20 |00:00:00.21 |   77171 |
|   2 |   VIEW                          |               |      1 |     20 |     20 |00:00:00.21 |   77171 |
|   3 |    NESTED LOOPS                 |               |      1 |     20 |     20 |00:00:00.21 |   77171 |
|   4 |     NESTED LOOPS                |               |      1 |    400 |  38129 |00:00:00.12 |   39042 |
|   5 |      TABLE ACCESS BY INDEX ROWID| T_ORD_BIG     |      1 |   1000K|  38129 |00:00:00.09 |   38242 |
|   6 |       INDEX FULL SCAN DESCENDING| IX_ORD_BIG_DT |      1 |    400 |  38129 |00:00:00.01 |     113 |
|*  7 |      INDEX UNIQUE SCAN          | PK_M_CUS      |  38129 |      1 |  38129 |00:00:00.02 |     800 |
|*  8 |     TABLE ACCESS BY INDEX ROWID | M_CUS         |  38129 |      1 |     20 |00:00:00.06 |   38129 |
-----------------------------------------------------------------------------------------------------

Peeked Binds (identified by position):
--------------------------------------
   1 - :TAIL (VARCHAR2(32), CSID=873): '1234'

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(ROWNUM<=20)
   7 - access("O"."CUS_ID"="C"."CUS_ID")
   8 - filter("C"."MBL_NO" LIKE '%'||:TAIL)

문제 ​

  1. 옵티마이저가 날짜 인덱스를 거꾸로 읽는 계획을 고른 이유를 MBL_NO 조건의 추정으로 설명하고, 실제로는 왜 38,129건을 읽고서야 멈췄는지 쓰시오.
  2. 화면은 그대로 네 자리를 받는다는 조건에서 추정이 맞도록 SQL과 통계를 고치시오. 다른 방법 하나와 그 대가도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자