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

상품 루프를 한 문장으로 바꿨는데 당일 상품별 집계가 한 번에 33만 블록을 읽는다

중급INSERT한빛유통165 / 700

상황 ​

「당일 상품별 판매 현황」은 5분마다 오늘 주문을 상품별로 묶어 요약 테이블에 다시 적재한다(하루 288번). 예전에는 상품 100개를 하나씩 돌며 집계했는데, 지난달 조민우가 INSERT ... SELECT 한 문장으로 바꿨다. 조민우는 "상품별 집계라서 상품부터 읽도록 힌트를 달았다"라고 한다. 적재 한 번은 1초 남짓이지만, 하루 288번 돌면서 서버의 논리 읽기 상위에 올라 있다. 아래는 2024-06-14 한 번의 실행계획이다.

테이블과 인덱스 ​

  • T_ORD: 100,000행 · 896블록 · PK_T_ORD (ORD_SEQ) · IX_T_ORD_DT (ORD_DT) · 하루 137건이고, 하루치 주문은 테이블 전체에 흩어져 있다
  • T_ORD_DET: 200,000행 · 896블록 · PK_T_ORD_DET (ORD_SEQ, ORD_DET_NO) · IX_T_ORD_DET_ITM (ITM_ID) (상품 화면용) · 상품마다 2,000줄 · 2024-06-14 주문상세는 274줄 (상품 30종)
  • M_ITM: 100행 · 5블록 · PK_M_ITM (ITM_ID)
  • 요약 테이블 T_ITM_DAY_SLS (BAS_DT, ITM_ID, ORD_CNT, ORD_QTY, ORD_AMT): 적재 전에 그날 행을 지운다 · 인덱스 없음 · 통계 최신

원본 SQL ​

sql
INSERT INTO T_ITM_DAY_SLS (BAS_DT, ITM_ID, ORD_CNT, ORD_QTY, ORD_AMT)
SELECT /*+ LEADING(m d o) USE_NL(d o) */
       :v_dt, m.ITM_ID, COUNT(DISTINCT o.ORD_SEQ), SUM(d.ORD_QTY), SUM(d.ORD_QTY * d.UNT_PRC)
  FROM M_ITM m, T_ORD_DET d, T_ORD o
 WHERE d.ITM_ID  = m.ITM_ID
   AND o.ORD_SEQ = d.ORD_SEQ
   AND o.ORD_DT >= :v_dt
   AND o.ORD_DT <  :v_dt + 1
 GROUP BY m.ITM_ID

실행계획 학습용 예시 · 행 수는 실데이터 ​

xplan
----------------------------------------------------------------------------------------------------------------
| Id  | Operation                         | Name             | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT                  |                  |      1 |        |      0 |00:00:01.21 |    331K |
|   1 |  LOAD TABLE CONVENTIONAL          | T_ITM_DAY_SLS    |      1 |        |      0 |00:00:01.21 |    331K |
|   2 |   HASH GROUP BY                   |                  |      1 |    100 |     30 |00:00:01.21 |    331K |
|   3 |    NESTED LOOPS                   |                  |      1 |    274 |    274 |00:00:01.20 |    331K |
|   4 |     NESTED LOOPS                  |                  |      1 |   200K |   200K |00:00:00.52 |    131K |
|   5 |      NESTED LOOPS                 |                  |      1 |   200K |   200K |00:00:00.31 |   90205 |
|   6 |       INDEX FULL SCAN             | PK_M_ITM         |      1 |    100 |    100 |00:00:00.01 |       1 |
|   7 |       TABLE ACCESS BY INDEX ROWID | T_ORD_DET        |    100 |   2000 |   200K |00:00:00.28 |   90204 |
|*  8 |        INDEX RANGE SCAN           | IX_T_ORD_DET_ITM |    100 |   2000 |   200K |00:00:00.04 |     604 |
|*  9 |      INDEX UNIQUE SCAN            | PK_T_ORD         |   200K |      1 |   200K |00:00:00.15 |   40612 |
|* 10 |     TABLE ACCESS BY INDEX ROWID   | T_ORD            |   200K |      1 |    274 |00:00:00.62 |    200K |
----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   8 - access("D"."ITM_ID"="M"."ITM_ID")
   9 - access("O"."ORD_SEQ"="D"."ORD_SEQ")
  10 - filter(("O"."ORD_DT">=:V_DT AND "O"."ORD_DT"<:V_DT+1))

문제 ​

  1. 6번부터 10번 줄의 Starts와 A-Rows를 따라가며, 그날 주문상세 274줄을 얻으려고 몇 행을 거쳤는지 설명하시오.
  2. 조인 순서를 다시 정해 개선 SQL을 작성하시오. "상품별로 묶으니 상품부터 읽는다"는 판단이 왜 틀렸는지 서술하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자