테마
매달 1일 새벽에 고객 등급 재산정 배치가 돈다. 최근 12개월 주문 금액으로 등급(300만 원 이상 A, 100만 원 이상 B, 그 밖 C)을 다시 매기고, 등급이 바뀐 고객은 이력을 남기고 문자를 보낸다. 1월 1일 실행은 3분 38초가 걸렸고 바뀐 고객은 214명이었다. 박팀장: "고객이 2,000명뿐인데 왜 이렇게 오래 걸립니까? 문자 발송 때문인가요?" 마케팅팀은 발송 패키지는 게이트웨이 계약 때문에 손댈 수 없다고 한다.
T_ORD_BIG: 1,000,000행 · 5,826블록 · 2024년 주문 333,612건. 인덱스는 IX_ORD_BIG_DT_AMT (ORD_DT, ORD_AMT) 하나다(CUS_ID 인덱스 없음).M_CUS: 2,000행 · 30블록 · PK_M_CUS (CUS_ID) · CUS_GD. 1년 동안 주문이 없는 고객도 있을 수 있다(그 고객은 C).T_CUS_GD_HIST(등급 이력, 이 문제용 테이블): CUS_ID · CHG_DT · OLD_GD · NEW_GD. 회차 번호 BAT_ID와 발송 여부 SENT_YN 칸을 더해도 된다.PKG_NOTI.SEND_GRADE_MSG(p_cus_id, p_new_gd): 마케팅팀 패키지. 발송 대기열 T_MSG_OUT에 한 행을 넣고 UTL_HTTP로 문자 게이트웨이를 부른다(한 번 약 35ms). 안에서 커밋하지 않는다. 보낸 문자는 되돌릴 수 없고, 같은 요청이 두 번 오면 게이트웨이는 두 번 보낸다.PROCEDURE REGRADE(p_base DATE) IS -- p_base = DATE '2025-01-01' (이번 달 1일)
v_amt NUMBER;
v_new M_CUS.CUS_GD%TYPE;
BEGIN
FOR c IN (SELECT CUS_ID, CUS_GD FROM M_CUS) LOOP
-- ① 최근 12개월 실적
SELECT NVL(SUM(ORD_AMT), 0) INTO v_amt
FROM T_ORD_BIG
WHERE CUS_ID = c.CUS_ID
AND ORD_DT >= ADD_MONTHS(p_base, -12)
AND ORD_DT < p_base;
-- ② 등급 판정과 갱신
v_new := CASE WHEN v_amt >= 3000000 THEN 'A'
WHEN v_amt >= 1000000 THEN 'B' ELSE 'C' END;
IF v_new <> c.CUS_GD THEN
UPDATE M_CUS SET CUS_GD = v_new WHERE CUS_ID = c.CUS_ID;
INSERT INTO T_CUS_GD_HIST (CUS_ID, CHG_DT, OLD_GD, NEW_GD)
VALUES (c.CUS_ID, p_base, c.CUS_GD, v_new);
-- ③ 알림
PKG_NOTI.SEND_GRADE_MSG(c.CUS_ID, v_new);
END IF;
COMMIT; -- 고객마다 (보낸 문자는 되돌릴 수 없으니까)
END LOOP;
END;
[세션 통계 — REGRADE(DATE '2025-01-01') 한 번 (운영 복제본)]
경과 시간 3분 38초
session logical reads 약 11,659,000
recursive calls 약 4,900
user commits 2,000
[V$SQL — 루프 안의 문장]
SQL_TEXT EXECUTIONS BUFFER_GETS ELAPSED
SELECT NVL(SUM(ORD_AMT), 0) FROM T_ORD_BIG WHERE CUS_ID = :B3 … 2,000 11,652,000 209.6 초
UPDATE M_CUS SET CUS_GD = :B2 WHERE CUS_ID = :B1 214 642 0.01 초
INSERT INTO T_CUS_GD_HIST (…) VALUES (…) 214 236 0.01 초
(PKG_NOTI.SEND_GRADE_MSG 214번 · 그 안의 UTL_HTTP 대기 7.4초)
[루프 안 SELECT 한 번의 실행계획 — DISPLAY_CURSOR (ALLSTATS LAST)]
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.10 | 5826 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.10 | 5826 |
|* 2 | TABLE ACCESS FULL| T_ORD_BIG | 1 | 167 | 166 |00:00:00.10 | 5826 |
-------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter(("CUS_ID"=:B3 AND "ORD_DT">=ADD_MONTHS(:B2,-12) AND "ORD_DT"<:B1))
V$SQL과 실행계획으로 설명하시오.