Skip to content
튜닝 연습조인 튜닝
JOIN-079

「지역 고객 오늘 주문」에 연간 누적 칸을 붙이고 나서 화면이 0.01초에서 1.7초가 됐다

중급SELECT한빛유통229 / 700

상황 ​

CRM 화면의 「지역 고객 오늘 주문」은 지역 하나를 고르면, 그 지역 고객 가운데 오늘 주문한 고객마다 오늘 주문 건수와 금액을 보여 준다. 2022년에 외주 업체가 튜닝하면서 실행계획의 Outline에서 힌트 한 줄을 옮겨 SQL 맨 앞에 붙였다. 지난주 CRM팀이 "최근 12개월 누적 금액" 칸을 더해 달라고 했고, 개발자가 같은 모양의 인라인 뷰를 하나 더 만들어 FROM 절에 끼웠다. 새 뷰만 따로 돌리면 0.1초 남짓인데 화면 전체는 1.7초가 걸린다. 아래는 2024-06-14, 지역 RGN03으로 조회한 실행계획이다.

테이블과 인덱스 ​

  • T_ORD_BIG: 1,000,000행 · 5,826블록 · 하루 약 913건 · 최근 12개월 333,029행 · 적재 순서가 날짜와 무관하다
  • 인덱스 IX_ORD_BIG_DT (ORD_DT): 리프 2,653블록 · 클러스터링 팩터 약 1,000,000
  • M_CUS: 2,000행 · 30블록 · RGN_ID 8종(지역마다 250명)
  • 통계 최신. 변경 전 화면은 947블록 · 0.01초였다

원본 SQL ​

sql
SELECT /*+ INDEX(@SEL$2 o IX_ORD_BIG_DT) */
       c.CUS_ID, c.CUS_NM, t.ord_cnt, t.ord_amt, y.yr_amt
  FROM M_CUS c,
       (SELECT o.CUS_ID, SUM(o.ORD_AMT) AS yr_amt              -- 이번에 더한 뷰
          FROM T_ORD_BIG o
         WHERE o.ORD_DT >= ADD_MONTHS(:v_dt, -12) + 1
           AND o.ORD_DT <  :v_dt + 1
         GROUP BY o.CUS_ID) y,
       (SELECT o.CUS_ID, COUNT(*) AS ord_cnt, SUM(o.ORD_AMT) AS ord_amt
          FROM T_ORD_BIG o
         WHERE o.ORD_DT >= :v_dt
           AND o.ORD_DT <  :v_dt + 1
         GROUP BY o.CUS_ID) t
 WHERE c.RGN_ID = :rgn
   AND t.CUS_ID = c.CUS_ID
   AND y.CUS_ID = c.CUS_ID

실행계획 학습용 예시 ​

xplan
--------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                               | Name          | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
--------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                        |               |      1 |        |     92 |00:00:01.71 |    335K |          |
|*  1 |  HASH JOIN                              |               |      1 |     91 |     92 |00:00:01.71 |    335K | 1180K (0)|
|*  2 |   HASH JOIN                             |               |      1 |     91 |     92 |00:00:00.01 |     947 | 1236K (0)|
|*  3 |    TABLE ACCESS FULL                    | M_CUS         |      1 |    250 |    250 |00:00:00.01 |      30 |          |
|   4 |    VIEW                                 |               |      1 |    730 |    731 |00:00:00.01 |     917 |          |
|   5 |     HASH GROUP BY                       |               |      1 |    730 |    731 |00:00:00.01 |     917 | 1290K (0)|
|   6 |      TABLE ACCESS BY INDEX ROWID BATCHED| T_ORD_BIG     |      1 |    913 |    913 |00:00:00.01 |     917 |          |
|*  7 |       INDEX RANGE SCAN                  | IX_ORD_BIG_DT |      1 |    913 |    913 |00:00:00.01 |       4 |          |
|   8 |   VIEW                                  |               |      1 |   2000 |   2000 |00:00:01.70 |    334K |          |
|   9 |    HASH GROUP BY                        |               |      1 |   2000 |   2000 |00:00:01.70 |    334K | 1420K (0)|
|  10 |     TABLE ACCESS BY INDEX ROWID BATCHED | T_ORD_BIG     |      1 |    333K|    333K|00:00:01.55 |    334K |          |
|* 11 |      INDEX RANGE SCAN                   | IX_ORD_BIG_DT |      1 |    333K|    333K|00:00:00.09 |     884 |          |
--------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("Y"."CUS_ID"="C"."CUS_ID")
   2 - access("T"."CUS_ID"="C"."CUS_ID")
   3 - filter("C"."RGN_ID"=:RGN)
   7 - access("O"."ORD_DT">=:V_DT AND "O"."ORD_DT"<:V_DT+1)
  11 - access("O"."ORD_DT">=ADD_MONTHS(:V_DT,-12)+1 AND "O"."ORD_DT"<:V_DT+1)

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
   1 - SEL$1
   3 - SEL$1 / C@SEL$1
   4 - SEL$3 / T@SEL$1
   5 - SEL$3
   6 - SEL$3 / O@SEL$3
   8 - SEL$2 / Y@SEL$1
   9 - SEL$2
  10 - SEL$2 / O@SEL$2

Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1
---------------------------------------------------------------------------

  10 -  SEL$2 / "O"@"SEL$2"
         -  INDEX(@SEL$2 o IX_ORD_BIG_DT)

문제 ​

  1. 새 뷰를 더한 뒤 10번과 11번 줄이 인덱스로 33만 행을 읽게 된 이유를, 힌트의 @SEL$2가 무엇을 가리키는지로 설명하시오. Query Block 칸과 Hint Report를 근거로 쓰시오.
  2. 블록이 더 늘어나도 힌트가 의도한 블록에만 걸리도록 SQL을 고치시오. 고친 뒤의 일량과 남는 일량도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자