테마
지난달 영수증 합계 함수 사건(뷰가 합쳐지면서 함수가 세 번씩 호출된 일) 뒤에 오과장이 코딩 가이드에 "인라인 뷰와 집계 뷰를 조인할 때는 NO_MERGE를 붙인다"라는 조문을 넣었다. 개발팀은 공통 뷰 V_SLE_RCPT(영수증별 품목 수와 금액)를 쓰는 SQL 세 개에 이 힌트를 붙였다.
(A) 「영수증 상세」는 영수증 번호로 하루 3,000번, (B) 「매장 일별 영수증」은 매장과 날짜로 하루 2,000번, (C) 「월간 매장 객단가」 리포트는 하루 30번 돈다. (A)와 (B)는 그대로인데 (C)만 0.2초에서 1.4초로 늘고 TEMP를 쓰기 시작했다. 오과장은 조문을 지킬지, 고칠지 숫자로 판단해 달라고 했다.
CF10 판매(영수증): 500,000행 · 4,583블록 · CF10_PK (SLE_NO) · IX_CF10_SLE_DT (SLE_DT) · 하루 약 700건, 2023년 12월 20,864건 · 매장 6곳CF11 판매상품: 1,096,708행 · 8,204블록 · CF11_PK (SLE_NO, ITM_NO) 높이 3 · 영수증 한 장에 평균 2.2줄(한 블록 안) · 12월 판매상품 45,935줄-- 공통 뷰: 영수증별 품목 수와 금액
CREATE OR REPLACE VIEW V_SLE_RCPT AS
SELECT SLE_NO, COUNT(*) AS ITM_CNT, SUM(SLE_QTY * SLE_UPRC) AS RCPT_AMT
FROM CF11
GROUP BY SLE_NO;
-- (A) 영수증 상세 (하루 3,000번)
SELECT /*+ NO_MERGE(v) */ h.SLE_NO, h.SLE_DT, h.SLE_HMS, v.ITM_CNT, v.RCPT_AMT
FROM CF10 h, V_SLE_RCPT v
WHERE h.SLE_NO = :sle_no
AND v.SLE_NO = h.SLE_NO;
-- (B) 매장 일별 영수증 (하루 2,000번)
SELECT /*+ NO_MERGE(v) */ h.SLE_NO, h.SLE_HMS, v.ITM_CNT, v.RCPT_AMT
FROM CF10 h, V_SLE_RCPT v
WHERE h.SHP_NO = :shp
AND h.SLE_DT = :dt
AND v.SLE_NO = h.SLE_NO;
-- (C) 월간 매장 객단가 (하루 30번)
SELECT /*+ NO_MERGE(v) */ h.SHP_NO, COUNT(*) AS RCPT_CNT, ROUND(AVG(v.RCPT_AMT)) AS AVG_AMT
FROM CF10 h, V_SLE_RCPT v
WHERE h.SLE_DT BETWEEN :m1 AND :m2
AND v.SLE_NO = h.SLE_NO
GROUP BY h.SHP_NO;
[C · :m1 = '20231201', :m2 = '20231231']
------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads | Writes | Used-Mem | Used-Tmp|
------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 6 |00:00:01.42 | 8494 | 3318 | 3318 | | |
| 1 | HASH GROUP BY | | 1 | 6 | 6 |00:00:01.42 | 8494 | 3318 | 3318 | 1036K (0)| |
|* 2 | HASH JOIN | | 1 | 20833 | 20864 |00:00:01.42 | 8494 | 3318 | 3318 | 2147K (0)| |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| CF10 | 1 | 20833 | 20864 |00:00:00.01 | 290 | 0 | 0 | | |
|* 4 | INDEX RANGE SCAN | IX_CF10_SLE_DT | 1 | 20833 | 20864 |00:00:00.01 | 98 | 0 | 0 | | |
| 5 | VIEW | V_SLE_RCPT | 1 | 500K| 500K|00:00:01.35 | 8204 | 3318 | 3318 | | |
| 6 | HASH GROUP BY | | 1 | 500K| 500K|00:00:01.30 | 8204 | 3318 | 3318 | 20M (1)| 27M |
| 7 | TABLE ACCESS FULL | CF11 | 1 | 1096K| 1096K|00:00:00.06 | 8204 | 0 | 0 | | |
------------------------------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("V"."SLE_NO"="H"."SLE_NO")
4 - access("H"."SLE_DT">=:M1 AND "H"."SLE_DT"<=:M2)
[B · :shp = 1003, :dt = '20230601']
-----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 121 |00:00:00.01 | 496 |
| 1 | NESTED LOOPS | | 1 | 118 | 121 |00:00:00.01 | 496 |
|* 2 | TABLE ACCESS BY INDEX ROWID BATCHED| CF10 | 1 | 118 | 121 |00:00:00.01 | 12 |
|* 3 | INDEX RANGE SCAN | IX_CF10_SLE_DT | 1 | 706 | 706 |00:00:00.01 | 5 |
| 4 | VIEW PUSHED PREDICATE | V_SLE_RCPT | 121 | 1 | 121 |00:00:00.01 | 484 |
| 5 | SORT GROUP BY | | 121 | 1 | 121 |00:00:00.01 | 484 |
| 6 | TABLE ACCESS BY INDEX ROWID | CF11 | 121 | 2 | 266 |00:00:00.01 | 484 |
|* 7 | INDEX RANGE SCAN | CF11_PK | 121 | 2 | 266 |00:00:00.01 | 363 |
-----------------------------------------------------------------------------------------------------------------
2 - filter("H"."SHP_NO"=:SHP)
3 - access("H"."SLE_DT"=:DT)
7 - access("SLE_NO"="H"."SLE_NO")
[A] CF10_PK와 CF11_PK로 영수증 한 장 · Buffers 8 · 7번 줄에 해당하는 조건은 access("SLE_NO"=:SLE_NO)
NO_MERGE인데 (A)와 (B)는 멀쩡하고 (C)만 느려진 이유도 설명하시오.