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

고객 한 명의 배송 현황을 띄우는 데 배송 이력 24만 줄의 현재 상태를 다 매긴다

실전SELECT한빛유통367 / 700

상황 ​

작년 물류 개편 때 배송 상태가 바뀔 때마다 한 줄씩 쌓는 이력 테이블 T_ORD_DLV_HIS를 만들고, 주문마다 지금 상태는 뷰 V_ORD_DLV_CUR로 보게 했다. 주문 상세 화면(Q1)은 이 뷰를 주문번호로, 고객센터 상담 화면 「이 고객의 배송 현황」(Q2)은 고객번호로 읽는다. 물류팀의 야간 배치 「배송 상태별 건수」도 이 뷰를 조건 없이 읽는다. Q2는 하루 약 2만 번 열리고 AWR 논리 읽기 3위다. 박팀장은 뷰를 고치는 안, 인덱스를 넓히는 안, MAX로 다시 쓰는 안, 주문 테이블에 현재 상태 칸을 두는 안을 견주어 달라고 했다.

테이블과 인덱스 ​

  • T_ORD_DLV_HIS 배송 상태 이력(이 문제에서 쓰는 테이블 · T_ORD에서 파생): 240,000행 · 1,520블록 · ORD_SEQ · HIS_NO(주문마다 1부터) · CUS_ID(주문의 고객을 복사 · 바뀌지 않는다) · DLV_ST(접수 · 출고 · 배송중 · 배송완료 · 반품) · CHG_DT
  • 주문 10만 건마다 1 ~ 5줄(평균 2.4) · 하루 약 330줄씩 쌓인다 · 쌓인 순서대로 적재되어 한 고객의 이력은 테이블 곳곳의 블록에 흩어져 있다 · 고객마다 주문 약 50건, 이력 약 120줄
  • 인덱스 PK_T_ORD_DLV_HIS (ORD_SEQ, HIS_NO) · IX_DLV_HIS_CUS (CUS_ID, ORD_SEQ): 둘 다 BLEVEL 1 · 통계 최신
  • T_ORD 주문: 100,000행 · 896블록 · PK_T_ORD (ORD_SEQ) · 배송 상태 칸은 없다

원본 SQL ​

sql
-- 뷰 (물류 개편 때 만든 그대로)
CREATE OR REPLACE VIEW V_ORD_DLV_CUR AS
SELECT ORD_SEQ, CUS_ID, DLV_ST, CHG_DT
  FROM (SELECT h.ORD_SEQ, h.CUS_ID, h.DLV_ST, h.CHG_DT,
               ROW_NUMBER() OVER (PARTITION BY h.ORD_SEQ ORDER BY h.HIS_NO DESC) AS rn
          FROM T_ORD_DLV_HIS h)
 WHERE rn = 1;

-- Q1 주문 상세 (빠르다)
SELECT DLV_ST, CHG_DT FROM V_ORD_DLV_CUR WHERE ORD_SEQ = :ord;

-- Q2 상담 화면 「이 고객의 배송 현황」 (하루 약 2만 번 · 느리다)
SELECT ORD_SEQ, DLV_ST, CHG_DT
  FROM V_ORD_DLV_CUR
 WHERE CUS_ID = :cus
 ORDER BY ORD_SEQ DESC;

-- 야간 배치 「배송 상태별 건수」 : SELECT DLV_ST, COUNT(*) FROM V_ORD_DLV_CUR GROUP BY DLV_ST;

실행계획 학습용 예시 ​

xplan
[Q1 · 주문 하나]
--------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name             | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
--------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |                  |      1 |        |      1 |00:00:00.01 |       4 |          |
|*  1 |  VIEW                                 |                  |      1 |      2 |      1 |00:00:00.01 |       4 |          |
|*  2 |   WINDOW SORT PUSHED RANK             |                  |      1 |      2 |      1 |00:00:00.01 |       4 | 2048  (0)|
|   3 |    TABLE ACCESS BY INDEX ROWID BATCHED| T_ORD_DLV_HIS    |      1 |      2 |      3 |00:00:00.01 |       4 |          |
|*  4 |     INDEX RANGE SCAN                  | PK_T_ORD_DLV_HIS |      1 |      2 |      3 |00:00:00.01 |       2 |          |
--------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("RN"=1)
   2 - filter(ROW_NUMBER() OVER ( PARTITION BY "H"."ORD_SEQ" ORDER BY INTERNAL_FUNCTION("H"."HIS_NO") DESC )<=1)
   4 - access("H"."ORD_SEQ"=:ORD)

[Q2 · 고객 CUS_000777 (주문 52건)]
---------------------------------------------------------------------------------------------------------------
| Id  | Operation                  | Name          | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
---------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT           |               |      1 |        |     52 |00:00:00.19 |    1520 |          |
|   1 |  SORT ORDER BY             |               |      1 |      2 |     52 |00:00:00.19 |    1520 | 4096  (0)|
|*  2 |   VIEW                     |               |      1 |    100K|     52 |00:00:00.19 |    1520 |          |
|*  3 |    WINDOW SORT PUSHED RANK |               |      1 |    240K|    100K|00:00:00.17 |    1520 | 6421K (0)|
|   4 |     TABLE ACCESS FULL      | T_ORD_DLV_HIS |      1 |    240K|    240K|00:00:00.02 |    1520 |          |
---------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter(("RN"=1 AND "CUS_ID"=:CUS))
   3 - filter(ROW_NUMBER() OVER ( PARTITION BY "H"."ORD_SEQ" ORDER BY INTERNAL_FUNCTION("H"."HIS_NO") DESC )<=1)

문제 ​

  1. Q2의 실행계획에서 고객 조건(CUS_ID = :cus)이 몇 번 줄에 걸려 있는지 짚고, 같은 뷰를 주문번호로 찾는 Q1은 4블록에 끝나는 까닭을 뷰의 PARTITION BY와 견주어 설명하시오.
  2. 상담 화면이 그 고객의 이력만 읽도록 뷰를 고치고, 고친 뷰가 Q1, Q2와 야간 배치의 결과를 바꾸지 않는 근거와 확인 방법을 쓰시오.
  3. 인덱스를 넓히는 안, MAX로 다시 쓰는 안, T_ORD에 현재 상태 칸을 두는 안을 2번 안과 견주어 일량과 대가로 비교하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자