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

반품 접수 화면이 주문 상세 세 줄의 그때 가격을 찾는 데 1,200블록을 읽는다

중급SELECT한빛유통204 / 700

상황 ​

고객센터의 「반품 접수」 화면은 주문번호를 넣으면 주문 상세 1 ~ 3줄과 줄마다 주문 시각의 판매가(최저가 연동 가격)를 보여 주고, 판매가와 실제 결제 단가의 차이로 환불액을 계산한다. 상담원이 하루 2천 번쯤 연다. 가격 이력이 날마다 쌓이면서 이 화면이 조금씩 무거워져, 요즘은 주문 한 건에 1,200블록 넘게 읽는다. 아래는 2024-12-08 14:22에 들어온 주문(상세 3줄)으로 잰 실행계획이다.

테이블과 인덱스 ​

  • T_ORD: 100,000행 · 896블록 · PK_T_ORD (ORD_SEQ) · ORD_DT는 시분초 있음 / T_ORD_DET: 200,000행 · 896블록 · PK_T_ORD_DET (ORD_SEQ, ORD_DET_NO) · 주문당 1 ~ 3줄
  • T_ITM_PRC_LOG 상품 판매가 이력 (이 문제에서 쓰는 파생 테이블, 선분이력): ITM_ID · ST_DTM · ED_DTM (다음 구간의 시작 시각, 이 시각 미만까지) · SLE_PRC · 최저가 연동 배치가 2023-01-01부터 매일 04시에 상품마다 새 구간을 연다 · 73,100행 · 410블록 · 상품마다 약 731구간
  • 인덱스 IX_ITM_PRC_LOG (ITM_ID, ST_DTM): BLEVEL 1 · 리프 약 230블록(상품 하나에 약 2.3블록) · 날마다 100행씩 쌓여서 상품 하나의 구간은 테이블 거의 전체에 흩어져 있다
  • 구간은 겹치거나 끊기지 않는다(매일 아침 점검으로 확인한다) · 통계 최신

원본 SQL ​

sql
SELECT d.ORD_DET_NO, d.ITM_ID, d.ORD_QTY, d.UNT_PRC, h.SLE_PRC AS LIST_PRC
  FROM T_ORD o, T_ORD_DET d, T_ITM_PRC_LOG h
 WHERE o.ORD_SEQ = :v_ord
   AND d.ORD_SEQ = o.ORD_SEQ
   AND h.ITM_ID  = d.ITM_ID
   AND h.ST_DTM <= o.ORD_DT
   AND h.ED_DTM >  o.ORD_DT
 ORDER BY d.ORD_DET_NO

실행계획 학습용 예시 ​

xplan
----------------------------------------------------------------------------------------------------------------
| Id  | Operation                              | Name           | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                       |                |      1 |        |      3 |00:00:00.03 |    1247 |
|   1 |  SORT ORDER BY                         |                |      1 |      2 |      3 |00:00:00.03 |    1247 |
|   2 |   NESTED LOOPS                         |                |      1 |      2 |      3 |00:00:00.03 |    1247 |
|   3 |    NESTED LOOPS                        |                |      1 |    730 |   2128 |00:00:00.01 |      17 |
|   4 |     NESTED LOOPS                       |                |      1 |      2 |      3 |00:00:00.01 |       7 |
|   5 |      TABLE ACCESS BY INDEX ROWID       | T_ORD          |      1 |      1 |      1 |00:00:00.01 |       3 |
|*  6 |       INDEX UNIQUE SCAN                | PK_T_ORD       |      1 |      1 |      1 |00:00:00.01 |       2 |
|   7 |      TABLE ACCESS BY INDEX ROWID BATCHED| T_ORD_DET     |      1 |      2 |      3 |00:00:00.01 |       4 |
|*  8 |       INDEX RANGE SCAN                 | PK_T_ORD_DET   |      1 |      2 |      3 |00:00:00.01 |       2 |
|*  9 |     INDEX RANGE SCAN                   | IX_ITM_PRC_LOG |      3 |    365 |   2128 |00:00:00.01 |      10 |
|* 10 |    TABLE ACCESS BY INDEX ROWID         | T_ITM_PRC_LOG  |   2128 |      1 |      3 |00:00:00.02 |    1230 |
----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   6 - access("O"."ORD_SEQ"=:V_ORD)
   8 - access("D"."ORD_SEQ"=:V_ORD)
   9 - access("H"."ITM_ID"="D"."ITM_ID" AND "H"."ST_DTM"<="O"."ORD_DT")
  10 - filter("H"."ED_DTM">"O"."ORD_DT")

문제 ​

  1. 상세 3줄의 가격 3개를 얻는 데 1,247블록을 읽은 까닭을 9번과 10번 줄로 설명하고, 가격 이력이 쌓이면 이 일량이 어떻게 변하는지 쓰시오.
  2. 인덱스를 새로 만들지 않고, 상세 한 줄마다 가격 구간을 한 건만 읽고 멈추도록 SQL을 고치시오. 이 방법이 맞으려면 데이터에 어떤 전제가 필요한지도 쓰시오.
  3. 인덱스를 (ITM_ID, ED_DTM, ST_DTM)으로 하나 더 만드는 안과 견주어, 어느 쪽을 고를지 근거와 함께 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자