테마
작년 물류 개편 때 배송 상태가 바뀔 때마다 한 줄씩 쌓는 이력 테이블 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_DTPK_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) · 배송 상태 칸은 없다-- 뷰 (물류 개편 때 만든 그대로)
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;
[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)
CUS_ID = :cus)이 몇 번 줄에 걸려 있는지 짚고, 같은 뷰를 주문번호로 찾는 Q1은 4블록에 끝나는 까닭을 뷰의 PARTITION BY와 견주어 설명하시오.MAX로 다시 쓰는 안, T_ORD에 현재 상태 칸을 두는 안을 2번 안과 견주어 일량과 대가로 비교하시오.