테마
작년에 만든 뷰 V_CUS_LAST_ORD는 고객마다 마지막 주문 한 건을 돌려준다. 이 뷰를 세 곳이 쓴다. [S1] 고객 상세 화면(고객 한 명, 하루 2만 번), [S2] CRM의 VIP 목록(A 등급 20명, 하루 3천 번), [S3] 야간 배치의 고객 요약 적재(전 고객 2,000명, 하루 한 번)다.
S2가 AWR의 읽은 블록 수 상위에 올라왔다. 개발팀은 "S1과 똑같은 뷰를 쓰는데 왜 S2만 느린지 모르겠다"라고 한다. 박팀장은 세 곳을 다 보고 고칠 것과 그대로 둘 것을 숫자로 정해 달라고 했다. 정산팀은 뷰가 같은 시각의 주문 두 건 가운데 어느 것을 '마지막'으로 내는지 정해져 있지 않다는 점도 지적했다.
T_ORD: 100,000행 · 896블록 · PK_T_ORD (ORD_SEQ) · IX_T_ORD_CUS_DT (CUS_ID, ORD_DT) (고객 상담 화면용, BLEVEL 1, 리프 약 330블록) · ORD_DT 시분초 있음 · 고객마다 50건이 테이블 전체에 흩어져 있다M_CUS: 2,000행 · 30블록 · PK_M_CUS (CUS_ID) · A 등급 20명 · CUS_GD에 히스토그램 있음-- 공용 뷰
CREATE OR REPLACE VIEW V_CUS_LAST_ORD AS
SELECT CUS_ID, ORD_SEQ, ORD_DT, ORD_ST, ORD_AMT
FROM (SELECT o.CUS_ID, o.ORD_SEQ, o.ORD_DT, o.ORD_ST, o.ORD_AMT,
ROW_NUMBER() OVER (PARTITION BY o.CUS_ID ORDER BY o.ORD_DT DESC) AS RN
FROM T_ORD o)
WHERE RN = 1;
-- [S1] 고객 상세: 고객 한 명 (하루 2만 번)
SELECT c.CUS_ID, c.CUS_NM, v.ORD_SEQ, v.ORD_DT, v.ORD_AMT
FROM M_CUS c, V_CUS_LAST_ORD v
WHERE c.CUS_ID = :cus
AND v.CUS_ID = c.CUS_ID;
-- [S2] VIP 목록: A 등급 20명 (하루 3천 번)
SELECT c.CUS_ID, c.CUS_NM, v.ORD_SEQ, v.ORD_DT, v.ORD_ST, v.ORD_AMT
FROM M_CUS c, V_CUS_LAST_ORD v
WHERE c.CUS_GD = 'A'
AND v.CUS_ID = c.CUS_ID
ORDER BY v.ORD_DT DESC;
-- [S3] 야간 고객 요약: 전 고객 (하루 한 번)
INSERT INTO T_CUS_SUMMARY (CUS_ID, LAST_ORD_SEQ, LAST_ORD_DT, LAST_ORD_AMT)
SELECT CUS_ID, ORD_SEQ, ORD_DT, ORD_AMT FROM V_CUS_LAST_ORD;
[S1] Buffers 5 · INDEX RANGE SCAN DESCENDING (IX_T_ORD_CUS_DT) → WINDOW NOSORT STOPKEY · 0.001초
[S2]
-----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Used-Mem |
-----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 20 |00:00:00.21 | 926 | |
| 1 | SORT ORDER BY | | 1 | 20 | 20 |00:00:00.21 | 926 | 2048 (0) |
|* 2 | HASH JOIN | | 1 | 20 | 20 |00:00:00.21 | 926 | 1269K (0) |
|* 3 | TABLE ACCESS FULL | M_CUS | 1 | 20 | 20 |00:00:00.01 | 30 | |
|* 4 | VIEW | | 1 | 100K| 2000 |00:00:00.20 | 896 | |
|* 5 | WINDOW SORT PUSHED RANK| | 1 | 100K| 2000 |00:00:00.19 | 896 | 4614K (0) |
| 6 | TABLE ACCESS FULL | T_ORD | 1 | 100K| 100K|00:00:00.01 | 896 | |
-----------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("V"."CUS_ID"="C"."CUS_ID")
3 - filter("C"."CUS_GD"='A')
4 - filter("RN"=1)
5 - filter(ROW_NUMBER() OVER ( PARTITION BY "O"."CUS_ID" ORDER BY INTERNAL_FUNCTION("O"."ORD_DT") DESC )<=1)
[S3] Buffers 926 + 적재 · WINDOW SORT PUSHED RANK 100K행 · 0.24초 (하루 한 번)
ROW_NUMBER가 S2의 조인 조건을 받지 못하는 이유를 포함하시오.