테마
이 문제는 교재 18화 졸업 과제와 같은 이커머스 데이터베이스를 씁니다. 『튜너의 1년』을 순서대로 읽고 있다면 졸업 과제를 먼저 풀고 오세요. 아래 「테이블과 인덱스」 칸에 과제의 답 일부(제약 조건, 인덱스, 통계의 실제 상태)가 나옵니다.
이커머스 개발팀은 매일 02:00에 배치 프레임워크로 「휴면 예정 안내」 잡을 돌린다. 11개월 동안 주문이 없는 고객을 임시 테이블에 담고(①), 메일 발송 대기열에 넣고(②), 고객에 안내일을 표시한다(③). 9월에는 대기열이 매일 0건이었다. 개발자가 임시 테이블을 ON COMMIT PRESERVE ROWS로 바꾼 뒤로는 날마다 건수가 들쭉날쭉하다.
10월 24일 고객센터에 "휴면 안내 메일을 이틀 사이에 두 번 받았다"는 문의가 몰렸다. 개발자: "임시 테이블이 세션마다 따로라던데, 우리 잡은 한 프로그램이 도는 거니까 세션도 하나 아닌가요?"
customers: 5,230행 · 51블록 · PK customer_id · UQ email. 이 문제에서는 휴면 예정 안내일 dormant_notice_dt DATE 칸이 더 있다(NULL이면 아직 안내 전).orders: 34,908행 · 193블록 · order_date DATE(시분초 있음) · PK만 있다.gt_dormant_target(전역 임시 테이블, 이 문제용): 원본 SQL 맨 위의 DDL이다. 9월까지는 ON COMMIT 절이 없었다.notice_queue(메일 발송 대기열, 이 문제용): 발송 서버가 5분마다 읽어 보낸다. 보낸 메일은 되돌릴 수 없다.-- 임시 테이블 (지난달 수정: ON COMMIT 절 없음 → PRESERVE ROWS)
CREATE GLOBAL TEMPORARY TABLE gt_dormant_target (
customer_id NUMBER,
email VARCHAR2(200),
last_order_dt DATE
) ON COMMIT PRESERVE ROWS;
-- 잡 「휴면 예정 안내」 · 단계마다 풀에서 연결을 빌려 실행하고 커밋한다 (:base_dt = 실행일 0시)
-- step1
INSERT INTO gt_dormant_target (customer_id, email, last_order_dt)
SELECT c.customer_id, c.email, MAX(o.order_date)
FROM customers c JOIN orders o ON o.customer_id = c.customer_id
WHERE c.dormant_notice_dt IS NULL
GROUP BY c.customer_id, c.email
HAVING MAX(o.order_date) < ADD_MONTHS(:base_dt, -11);
-- step2
INSERT INTO notice_queue (customer_id, email, notice_tp, req_dt)
SELECT customer_id, email, 'DORMANT', SYSDATE
FROM gt_dormant_target;
-- step3
UPDATE customers
SET dormant_notice_dt = :base_dt
WHERE customer_id IN (SELECT customer_id FROM gt_dormant_target);
[잡 실행 기록 — 단계별 처리 건수와 빌려 쓴 세션]
실행일 step1 (SID · 건수) step2 (SID · 건수) step3 (SID · 건수)
10/21 412 · 298 412 · 298 415 · 0
10/22 415 · 305 388 · 0 412 · 298
10/23 412 · 13 412 · 311 412 · 311
[10/24 아침에 센 것]
이틀 사이 같은 메일을 두 번 받은 고객 298명 (10/21 · 10/23)
세션 415 의 gt_dormant_target 에 남은 행 305행 (10/22 step1 이 넣은 것)
[9월 — ON COMMIT 절이 없던 때 (매일 같은 모양)]
step1 290 ~ 300 · step2 0 · step3 0
[딕셔너리]
SELECT temporary, duration FROM user_tables WHERE table_name = 'GT_DORMANT_TARGET';
Y SYS$SESSION