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

시간대 분석 화면 세 개 가운데 둘이 주문상품을 21만 번씩 찾아간다

실전SELECT버거615 / 700

상황 ​

버거 본사 마케팅팀이 시간대 분석 화면 세 개를 사내에서 새로 만들었다. 점심 피크 메뉴(R1), 12시대 매장별 주문 수(R2), 저녁 피크 메뉴(R3)다. 개발자는 "시각 문자열의 앞 두 자리만 잘라 비교하면 간단하다"고 했고, R3은 다른 개발자가 숫자로 바꿔 비교하게 썼다. R1과 R3은 3초 가까이 걸리고, R2는 0.2초다. 다음 주 메뉴 개편 회의 전까지 하루 수십 번씩 돌린다. 선배는 셋을 같이 보고 한 번에 고칠 길을 고르라고 했다. 세 SQL은 고칠 수 있다.

테이블과 인덱스 ​

  • ORD 주문: 1,000,000행 · 22,422블록 · ORD_HMS VARCHAR2(6) 'HH24MISS'(늘 6자리, 영업시간 09:00:00 ~ 22:59:59) · 시간대마다 고르게 쌓였다(한 시간에 약 71,429건)
  • ORD_ITEM 주문상품: 2,472,717행 · 55,265블록 · ORD_ITEM_PK (ORD_NO, ITEM_ID): BLEVEL 2 · 주문당 평균 2.47줄 · 주문번호 순으로 쌓였다 · 주문의 ORD_DT와 ORD_HMS를 그대로 복사해 둔 열이 있다
  • ITEM 상품: 92행 · 5블록
  • 통계 최신 · 히스토그램 없음 · 확장 통계 없음
  • 11 ~ 13시 주문 214,286건(21.4%) · 12시대 71,429건 · 18 ~ 20시 214,286건

원본 SQL ​

sql
-- R1 점심 피크 메뉴 (11시 ~ 13시대)
SELECT i.ITEM_NM, SUM(oi.ORD_ITEM_QTY) AS qty
  FROM ORD o, ORD_ITEM oi, ITEM i
 WHERE SUBSTR(o.ORD_HMS, 1, 2) BETWEEN '11' AND '13'
   AND oi.ORD_NO = o.ORD_NO
   AND i.ITEM_ID = oi.ITEM_ID
 GROUP BY i.ITEM_NM;

-- R2 12시대 매장별 주문 수
SELECT o.SHOP_NO, COUNT(*) AS ord_cnt
  FROM ORD o
 WHERE SUBSTR(o.ORD_HMS, 1, 2) = '12'
 GROUP BY o.SHOP_NO;

-- R3 저녁 피크 메뉴 (18시 ~ 20시대)
SELECT i.ITEM_NM, SUM(oi.ORD_ITEM_QTY) AS qty
  FROM ORD o, ORD_ITEM oi, ITEM i
 WHERE TO_NUMBER(o.ORD_HMS) BETWEEN 180000 AND 205959
   AND oi.ORD_NO = o.ORD_NO
   AND i.ITEM_ID = oi.ITEM_ID
 GROUP BY i.ITEM_NM;

실행계획 학습용 예시 ​

xplan
[R1]
----------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name        | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |             |      1 |        |     92 |00:00:02.91 |    667K |
|   1 |  HASH GROUP BY                 |             |      1 |     92 |     92 |00:00:02.91 |    667K |
|*  2 |   HASH JOIN                    |             |      1 |   6175 |    529K|00:00:02.85 |    667K |
|   3 |    TABLE ACCESS FULL           | ITEM        |      1 |     92 |     92 |00:00:00.01 |       5 |
|   4 |    NESTED LOOPS                |             |      1 |   6175 |    529K|00:00:02.71 |    667K |
|   5 |     NESTED LOOPS               |             |      1 |   6175 |    529K|00:00:01.32 |    452K |
|*  6 |      TABLE ACCESS FULL         | ORD         |      1 |   2500 |    214K|00:00:00.21 |   22422 |
|*  7 |      INDEX RANGE SCAN          | ORD_ITEM_PK |    214K|      2 |    529K|00:00:00.98 |    430K |
|   8 |     TABLE ACCESS BY INDEX ROWID| ORD_ITEM    |    529K|      1 |    529K|00:00:01.21 |    215K |
----------------------------------------------------------------------------------------------------
   2 - access("I"."ITEM_ID"="OI"."ITEM_ID")
   6 - filter((SUBSTR("O"."ORD_HMS",1,2)>='11' AND SUBSTR("O"."ORD_HMS",1,2)<='13'))
   7 - access("OI"."ORD_NO"="O"."ORD_NO")

[R2]  TABLE ACCESS FULL ORD · filter(SUBSTR("O"."ORD_HMS",1,2)='12') · E-Rows 10,000 / A-Rows 71,429
      → HASH GROUP BY · Buffers 22,422 · 0.2초
[R3]  R1과 같은 모양(ORD FULL → NL ORD_ITEM_PK) · filter(TO_NUMBER("O"."ORD_HMS")>=180000 AND
      TO_NUMBER("O"."ORD_HMS")<=205959) · E-Rows 2,500 / A-Rows 214,286 · Buffers 667K · 2.9초

문제 ​

  1. 세 SQL의 E-Rows(2,500 · 10,000 · 2,500)가 어디서 나왔는지 쓰고, 셋 다 틀렸는데 R2만 느리지 않은 이유를 설명하시오.
  2. 고치는 길(원래 열의 범위 조건으로 다시 쓰기, 표현식 통계, 가상 열)을 비교하고, 하나를 골라 세 SQL을 고쳐 쓰시오.
  3. 고른 길의 대가와 반영 전에 확인할 것을 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자