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

매장 칸을 비우고 조회하면 하루치 매장별 주문 수가 백 배 느려지고 합계도 모자란다

중급SELECT버거40 / 700

상황 ​

버거원 본사 운영팀의 「일별 매장 주문」은 날짜(필수)와 매장 번호(선택)를 받아 매장별 주문 수를 보여 준다. 매장 번호를 비우면 전 매장이 나온다. 개발자는 한 SQL로 두 경우를 다 받으려고 SHOP_NO LIKE :v_shop || '%'로 썼다. 매장 번호를 비우면 '%' 하나만 넘어간다. 매장을 넣으면 0.01초인데 비우면 0.4초가 걸린다. 오과장은 "키오스크 장애가 있던 날은 이 화면의 전 매장 합계가 일 마감 보고서의 주문 수보다 적다"는 민원도 함께 넘겼다. 아래는 같은 커서의 두 실행이다.

테이블과 인덱스 ​

  • ORD: 1,000,000행 · 22,422블록 · ORD_DT VARCHAR2(8) 2012-01-01 ~ 2013-12-31 · 날짜순 적재 · 하루 약 1,344건 · 매장 200곳('SH0001' ~ 'SH0200')
  • SHOP_NO는 NOT NULL 제약이 없다. 2013-03-02 ~ 03-05 키오스크 장애 때 매장 번호 없이 들어온 주문 1,204건이 있다 (문제 설정: 교재의 ORD에는 SHOP_NO가 빈 행이 없다)
  • 인덱스 ORD_X01 (ORD_DT, ORD_HMS): 리프 3,774 · CF 22,223 / IX_ORD_SHOP_DT (SHOP_NO, ORD_DT): 매장 화면용 · BLEVEL 2 · 리프 3,310 · 통계 최신
  • 이 커서는 아침에 매장 번호를 넣은 조회로 처음 파싱되었다

원본 SQL ​

sql
SELECT o.SHOP_NO, COUNT(*) AS day_cnt
  FROM ORD o
 WHERE o.ORD_DT = :v_dt
   AND o.SHOP_NO LIKE :v_shop || '%'
 GROUP BY o.SHOP_NO

실행계획 학습용 예시 ​

xplan
[첫 실행 · :v_shop = 'SH0012' · :v_dt = '20130614']
-------------------------------------------------------------------------------------------------
| Id  | Operation            | Name           | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |                |      1 |        |      1 |00:00:00.01 |      19 |
|   1 |  SORT GROUP BY NOSORT|                |      1 |      1 |      1 |00:00:00.01 |      19 |
|*  2 |   INDEX RANGE SCAN   | IX_ORD_SHOP_DT |      1 |      7 |      7 |00:00:00.01 |      19 |
-------------------------------------------------------------------------------------------------

[같은 커서 · 매장 칸을 비움(:v_shop = NULL → '%') · :v_dt = '20130614']
-------------------------------------------------------------------------------------------------
| Id  | Operation            | Name           | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |                |      1 |        |    200 |00:00:00.38 |    3313 |
|   1 |  SORT GROUP BY NOSORT|                |      1 |      1 |    200 |00:00:00.38 |    3313 |
|*  2 |   INDEX RANGE SCAN   | IX_ORD_SHOP_DT |      1 |      7 |   1344 |00:00:00.38 |    3313 |
-------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("O"."SHOP_NO" LIKE :V_SHOP||'%' AND "O"."ORD_DT"=:V_DT)
       filter(("O"."ORD_DT"=:V_DT AND "O"."SHOP_NO" LIKE :V_SHOP||'%'))

문제 ​

  1. 같은 커서인데 매장 칸을 비우면 19블록이 3,313블록으로 느는 이유를 실행계획으로 설명하시오. 비운 칸이 왜 첫 실행의 계획을 그대로 쓰는지도 설명하시오.
  2. 키오스크 장애가 있던 날 전 매장 합계가 모자라는 이유를 설명하시오.
  3. 두 경우가 각자 맞는 길로 읽도록 SQL을 고치시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자