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

정가를 당시 가격 이력으로 바꾸라는 감사 지적을 반영했더니 하반기 정가 점검이 정산 인덱스를 5만 번 찾아간다

중급SELECT한빛유통206 / 700

상황 ​

정산팀의 「하반기 정가 점검」은 정산이 끝난 주문의 상세를 상품별로 묶어 정가 매출과 실제 판매 금액을 견준다. 지금까지는 상품 마스터의 현재 단가를 정가로 썼는데, 감사에서 "주문 당시 가격을 써야 한다"는 지적을 받아 개발자가 가격 이력 조인을 하나 더했다. 그 뒤로 0.12초이던 점검이 1.4초가 됐다. Buffers는 2,400에서 15만으로 늘었는데, 조인 하나를 더한 것 말고는 바꾼 것이 없다. 힌트는 원래 없었다.

테이블과 인덱스 ​

  • T_ORD: 100,000행 · 896블록 · PK_T_ORD (ORD_SEQ) · 2024년 하반기 주문 25,062건 / T_ORD_DET: 200,000행 · 896블록 · PK_T_ORD_DET (ORD_SEQ, ORD_DET_NO) · 하반기 상세 50,124줄
  • T_ORD_STLM 정산: 100,000행 · 640블록 · 주문 한 건에 정산 한 건 · 인덱스 IX_STLM_ORD (ORD_SEQ)(정산 대사 화면용, BLEVEL 1) · 정산 마감일(:v_close) 전에 정산된 하반기 주문 24,310건
  • M_ITM_PRC_HIS 상품 가격 이력 (신시스템에 있는 테이블, 이 문제에서는 2023 ~ 2024년 개정분까지 쌓인 모양): ITM_ID · FR_YMD · TO_YMD (VARCHAR2(8), 그날까지 포함) · UNT_PRC · 상품 100개 × 6구간 = 600행 · 5블록 · PK_M_ITM_PRC_HIS (ITM_ID, TO_YMD) · 구간은 겹치거나 끊기지 않는다
  • 통계 최신 · 이력을 더하기 전 계획은 네 테이블 모두 해시 조인이었다(Buffers 약 2,400)

원본 SQL ​

sql
SELECT d.ITM_ID,
       COUNT(*)                   AS det_cnt,
       SUM(d.ORD_QTY * h.UNT_PRC) AS list_amt,
       SUM(d.ORD_QTY * d.UNT_PRC) AS sale_amt
  FROM T_ORD o, T_ORD_DET d, M_ITM_PRC_HIS h, T_ORD_STLM s
 WHERE o.ORD_DT >= DATE '2024-07-01'
   AND o.ORD_DT <  DATE '2025-01-01'
   AND d.ORD_SEQ = o.ORD_SEQ
   AND h.ITM_ID  = d.ITM_ID
   AND TO_CHAR(o.ORD_DT, 'YYYYMMDD') BETWEEN h.FR_YMD AND h.TO_YMD
   AND s.ORD_SEQ = o.ORD_SEQ
   AND s.STLM_DT < :v_close
 GROUP BY d.ITM_ID

실행계획 학습용 예시 ​

xplan
--------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name          | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
--------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |               |      1 |        |    100 |00:00:01.43 |     152K|
|   1 |  HASH GROUP BY                 |               |      1 |    100 |    100 |00:00:01.43 |     152K|
|   2 |   NESTED LOOPS                 |               |      1 |    731 |  48620 |00:00:01.36 |     152K|
|   3 |    NESTED LOOPS                |               |      1 |    752 |  50124 |00:00:00.29 |     102K|
|*  4 |     HASH JOIN                  |               |      1 |    752 |  50124 |00:00:00.11 |    1797 |
|   5 |      TABLE ACCESS FULL         | M_ITM_PRC_HIS |      1 |    600 |    600 |00:00:00.01 |       5 |
|*  6 |      HASH JOIN                 |               |      1 |  50300 |  50124 |00:00:00.08 |    1792 |
|*  7 |       TABLE ACCESS FULL        | T_ORD         |      1 |  25150 |  25062 |00:00:00.01 |     896 |
|   8 |       TABLE ACCESS FULL        | T_ORD_DET     |      1 |    200K|    200K|00:00:00.02 |     896 |
|*  9 |     INDEX RANGE SCAN           | IX_STLM_ORD   |  50124 |      1 |  50124 |00:00:00.15 |     100K|
|* 10 |    TABLE ACCESS BY INDEX ROWID | T_ORD_STLM    |  50124 |      1 |  48620 |00:00:01.02 |   50124 |
--------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   4 - access("H"."ITM_ID"="D"."ITM_ID")
       filter(("H"."FR_YMD"<=TO_CHAR(INTERNAL_FUNCTION("O"."ORD_DT"),'YYYYMMDD') AND
              "H"."TO_YMD">=TO_CHAR(INTERNAL_FUNCTION("O"."ORD_DT"),'YYYYMMDD')))
   6 - access("D"."ORD_SEQ"="O"."ORD_SEQ")
   7 - filter(("O"."ORD_DT">=TO_DATE(' 2024-07-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND
              "O"."ORD_DT"<TO_DATE(' 2025-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')))
   9 - access("S"."ORD_SEQ"="O"."ORD_SEQ")
  10 - filter("S"."STLM_DT"<:V_CLOSE)

문제 ​

  1. 가격 이력 조인 하나를 더했을 뿐인데 정산 쪽 조인이 NL로 바뀐 까닭을 4번 줄의 E-Rows와 A-Rows로 설명하시오. 옵티마이저가 그 숫자를 어떻게 얻었는지도 쓰시오.
  2. 이력 조인을 어디에 두어야 하는지 정하고, 그 순서와 조인 방식을 고정하는 SQL을 작성하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자