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

바인드를 모두 문자로 넘기는데도 「매장별 주문 순위」가 한 주만 골라도 주문 100만 건을 다 읽는다

기초SELECT버거24 / 700

상황 ​

버거원 본사 영업팀의 「매장별 주문 순위」 화면은 기간을 넣으면 매장 200곳을 주문 수 순으로 보여 준다. 「전체 기간」을 켜면 기간 칸을 무시하고 2012년 오픈 이후 전체를 센다. 영업팀 열 명이 하루 수백 번 여는데, 한 주만 골라도 0.4초가 넘게 걸린다. 조민우는 바인드 타입부터 확인했다. 앱은 세 값을 모두 setString으로 넘기고, V$SQL_BIND_CAPTURE에서도 셋 다 VARCHAR2다. "바인드는 다 문자인데 TO_NUMBER는 어디서 나온 거죠?" 「전체 기간」은 3년 전 개발자가 "가장 작은 값과 가장 큰 값을 넣으면 전체가 된다"며 DECODE로 넣은 것이다.

테이블과 인덱스 ​

  • ORD 주문: 1,000,000행 · 22,422블록 · 주문 일시 순으로 쌓였다 · ORD_DT VARCHAR2(8) '20120101' ~ '20131231'(모두 숫자 여덟 자리) · ORD_HMS · SHOP_NO VARCHAR2(10) 200곳 · 하루 약 1,370건
  • 2013-12-02 ~ 12-08 한 주의 주문: 9,604건
  • 인덱스 ORD_PK (ORD_NO) · ORD_X01 (ORD_DT, ORD_HMS): BLEVEL 2 · 리프 3,774블록 · CF 22,223 · 통계 최신
  • 앱은 :v_all(「전체 기간」이면 'Y', 아니면 'N')과 :v_from, :v_to('YYYYMMDD')를 모두 setString으로 넘긴다

원본 SQL ​

sql
SELECT SHOP_NO, COUNT(*) AS ord_cnt,
       RANK() OVER (ORDER BY COUNT(*) DESC) AS rnk
  FROM ORD
 WHERE ORD_DT BETWEEN DECODE(:v_all, 'Y', 0, :v_from)
                  AND DECODE(:v_all, 'Y', 99991231, :v_to)
 GROUP BY SHOP_NO
 ORDER BY rnk

실행계획 학습용 예시 ​

xplan
[2013-12-02 ~ 12-08 · 「전체 기간」 끔]
----------------------------------------------------------------------------------------
| Id  | Operation             | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |      |      1 |        |    200 |00:00:00.43 |   22426 |
|   1 |  WINDOW SORT          |      |      1 |    200 |    200 |00:00:00.43 |   22426 |
|   2 |   HASH GROUP BY       |      |      1 |    200 |    200 |00:00:00.43 |   22426 |
|*  3 |    FILTER             |      |      1 |        |   9604 |00:00:00.42 |   22426 |
|*  4 |     TABLE ACCESS FULL | ORD  |      1 |   2500 |   9604 |00:00:00.42 |   22426 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   3 - filter(DECODE(:V_ALL,'Y',99991231,TO_NUMBER(:V_TO))>=DECODE(:V_ALL,'Y',0,TO_NUMBER(:V_FROM)))
   4 - filter((TO_NUMBER("ORD_DT")>=DECODE(:V_ALL,'Y',0,TO_NUMBER(:V_FROM)) AND
              TO_NUMBER("ORD_DT")<=DECODE(:V_ALL,'Y',99991231,TO_NUMBER(:V_TO))))

[V$SQL_BIND_CAPTURE]
NAME     DATATYPE_STRING  VALUE_STRING
-------  ---------------  ------------
:V_ALL   VARCHAR2(32)     N
:V_FROM  VARCHAR2(32)     20131202
:V_TO    VARCHAR2(32)     20131208

문제 ​

  1. 바인드 셋이 모두 VARCHAR2인데도 4번 줄에 TO_NUMBER("ORD_DT")가 생긴 이유를 DECODE가 돌려주는 값의 타입으로 설명하시오.
  2. ORD_X01로 그 주의 범위만 읽도록 SQL을 고치시오.
  3. 같은 로직을 CASE로 썼다면 어떻게 되었을지, 그리고 지금 SQL이 느린 것 말고도 안고 있는 위험을 설명하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자