Skip to content
튜닝 연습소트 튜닝과 페이징
SORT-049

「매장 월 순위」 200줄이 1.4초씩 걸리고, 월초에는 논리 읽기 1위다

기초SELECT버거354 / 700

상황 ​

본사 영업관리팀의 「매장 월 순위」는 고른 달의 매장별 주문 수와 전체 대비 비중, 순위를 보여 준다. 주문 수가 같은 매장은 같은 순위를 받고 다음 순위는 그만큼 건너뛴다(공동 3위가 둘이면 다음은 5위). 매장 점장들도 자기 매장 순위를 보려고 열어서 월초 사흘 동안 하루 1,500번쯤 호출된다. 한 번 여는 데 1.4초가 걸리고, 월초에는 DBA가 뽑은 논리 읽기 순위에서 1위다. 아래는 2013년 12월로 연 실행계획이다.

테이블과 인덱스 ​

  • ORD 주문: 1,000,000행 · 22,422블록 · ORD_DT VARCHAR2(8) · SHOP_NO 매장 200개 · ORD_X01 (ORD_DT, ORD_HMS) · 주문일 순으로 쌓여 있다(CF 22,223).
  • 2013년 12월 주문은 42,408건이다. 인덱스 범위는 리프 약 140블록, 테이블은 951블록이다.
  • 매장별 12월 주문 수는 168 ~ 259건이고, 서로 다른 값은 67가지다(주문 수가 같은 매장이 여럿 있다).
  • SHOP_NO로 시작하는 인덱스는 없다. 통계는 최신이다.

원본 SQL ​

sql
SELECT s.SHOP_NO, s.ord_cnt,
       ROUND(s.ord_cnt * 100 / (SELECT COUNT(*)
                                  FROM ORD
                                 WHERE ORD_DT BETWEEN :d_from AND :d_to), 2) AS pct,
       (SELECT COUNT(*) + 1
          FROM (SELECT SHOP_NO, COUNT(*) AS ord_cnt
                  FROM ORD
                 WHERE ORD_DT BETWEEN :d_from AND :d_to
                 GROUP BY SHOP_NO) x
         WHERE x.ord_cnt > s.ord_cnt)                                      AS rnk
  FROM (SELECT SHOP_NO, COUNT(*) AS ord_cnt
          FROM ORD
         WHERE ORD_DT BETWEEN :d_from AND :d_to
         GROUP BY SHOP_NO) s
 ORDER BY rnk, s.SHOP_NO

실행계획 학습용 예시 ​

xplan
[2013년 12월]
-------------------------------------------------------------------------------------------------------------------
| Id  | Operation                               | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
-------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                        |         |      1 |        |    200 |00:00:01.42 |   74535 |          |
|   1 |  SORT AGGREGATE                         |         |      1 |      1 |      1 |00:00:00.01 |     143 |          |
|*  2 |   FILTER                                |         |      1 |        |  42408 |00:00:00.01 |     143 |          |
|*  3 |    INDEX RANGE SCAN                     | ORD_X01 |      1 |  42408 |  42408 |00:00:00.01 |     143 |          |
|   4 |  SORT AGGREGATE                         |         |     67 |      1 |     67 |00:00:01.36 |   73298 |          |
|*  5 |   VIEW                                  |         |     67 |     10 |   6633 |00:00:01.36 |   73298 |          |
|   6 |    HASH GROUP BY                        |         |     67 |    200 |  13400 |00:00:01.35 |   73298 | 1186K (0)|
|*  7 |     FILTER                              |         |     67 |        |   2841K|00:00:00.97 |   73298 |          |
|   8 |      TABLE ACCESS BY INDEX ROWID BATCHED| ORD     |     67 |  42408 |   2841K|00:00:00.71 |   73298 |          |
|*  9 |       INDEX RANGE SCAN                  | ORD_X01 |     67 |  42408 |   2841K|00:00:00.24 |    9581 |          |
|  10 |  SORT ORDER BY                          |         |      1 |    200 |    200 |00:00:01.42 |   74535 | 2048 (0) |
|  11 |   VIEW                                  |         |      1 |    200 |    200 |00:00:00.03 |    1094 |          |
|  12 |    HASH GROUP BY                        |         |      1 |    200 |    200 |00:00:00.03 |    1094 | 1186K (0)|
|* 13 |     FILTER                              |         |      1 |        |  42408 |00:00:00.02 |    1094 |          |
|  14 |      TABLE ACCESS BY INDEX ROWID BATCHED| ORD     |      1 |  42408 |  42408 |00:00:00.01 |    1094 |          |
|* 15 |       INDEX RANGE SCAN                  | ORD_X01 |      1 |  42408 |  42408 |00:00:00.01 |     143 |          |
-------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter(:D_FROM<=:D_TO)
   3 - access("ORD_DT">=:D_FROM AND "ORD_DT"<=:D_TO)
   5 - filter("X"."ORD_CNT">:B1)
   7 - filter(:D_FROM<=:D_TO)
   9 - access("ORD_DT">=:D_FROM AND "ORD_DT"<=:D_TO)
  13 - filter(:D_FROM<=:D_TO)
  15 - access("ORD_DT">=:D_FROM AND "ORD_DT"<=:D_TO)

문제 ​

  1. 화면은 200줄인데 74,535블록을 읽는 까닭을 실행계획의 Starts로 설명하시오. 200번이 아니라 67번인 까닭도 쓰시오.
  2. 12월 주문을 한 번만 읽도록 SQL을 고치시오. 주문 수가 같은 매장의 순위가 지금 화면과 같게 나오는 함수를 고르고 그 까닭을 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자