테마
이 문제는 교재 18화 졸업 과제와 같은 이커머스 데이터베이스를 씁니다. 『튜너의 1년』을 순서대로 읽고 있다면 졸업 과제를 먼저 풀고 오세요. 아래 「테이블과 인덱스」 칸에 과제의 답 일부(제약 조건, 인덱스, 통계의 실제 상태)가 나옵니다.
이커머스는 상품 상세 화면을 열 때마다 product_views_p에 한 행을 넣는다. 2025년 11월 28일 블랙프라이데이 오픈(10:00 ~ 10:20)에는 동시에 약 150세션이 조회 기록을 넣었고, 그동안 기록 저장이 평균 0.002초에서 0.09초로 늘어 상세 화면이 눈에 띄게 늦게 떴다.
지난해 같은 증상 때는 PK를 역방향 키 인덱스로 바꿔 인덱스 경합을 풀었다. DBA가 올해 AWR을 뽑아 왔다.
product_views_p 상품 조회 기록(파티션 복사본, 문제 설정): product_views 100,000행을 PARTITION BY RANGE (view_date) INTERVAL (NUMTOYMINTERVAL(1, 'MONTH'))로 나눠 CTAS로 지었다(STORAGE (INITIAL 64K)). 복사본에는 2020년 1월 ~ 2024년 12월의 60칸이 있고, 칸 하나는 약 1,670행, FULL로 약 7블록이다.SYS_P3307이다.view_id는 시퀀스 번호(CACHE 1000)다. customer_id는 비회원 조회면 NULL이다(약 30%). 블랙프라이데이에는 조회가 특가 상품 몇 개에 몰린다.pk_product_views_p (view_id) 하나다.-- 상세 화면을 열 때마다
INSERT INTO product_views_p (view_id, product_id, customer_id, view_date)
VALUES (seq_view_id.NEXTVAL, :product_id, :customer_id, SYSDATE);
-- 「월 상품 조회 순위」 (야간 배치 · :v_mon = 그달 첫날)
SELECT product_id, COUNT(*) AS view_cnt
FROM product_views_p
WHERE view_date >= :v_mon
AND view_date < ADD_MONTHS(:v_mon, 1)
GROUP BY product_id
ORDER BY view_cnt DESC;
[AWR · 2025-11-28 10:00 ~ 10:20 · 조회 기록 INSERT 186,400건 · 동시 약 150세션]
EVENT WAITS TIME(s) AVG(ms) %DB time
buffer busy waits 98,310 688 7 38.6
enq: HW - contention 21,740 413 19 23.2
DB CPU 402 22.6
log file sync 186,900 176 1 9.9
enq: TX - index contention 212 2 9 0.1
Segments by Buffer Busy Waits
OBJECT SUBOBJECT TYPE BUSY WAITS %Total
PRODUCT_VIEWS_P SYS_P3307 TABLE PARTITION 96,120 97.8
PK_PRODUCT_VIEWS_P INDEX 1,410 1.4
V$WAITSTAT (같은 구간 · 블록 종류)
data block 63% · 1st level bmb 24% · segment header 11% · 기타 2%
[「월 상품 조회 순위」 · 2024년 11월 (복사본)]
----------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Pstart | Pstop |
----------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 280 | 00:00:00.01 | 7 | | |
| 1 | SORT ORDER BY | | 1 | 280 | 280 | 00:00:00.01 | 7 | | |
| 2 | HASH GROUP BY | | 1 | 280 | 280 | 00:00:00.01 | 7 | | |
|* 3 | FILTER | | 1 | | 1668 | 00:00:00.01 | 7 | | |
| 4 | PARTITION RANGE ITERATOR | | 1 | 1667 | 1668 | 00:00:00.01 | 7 | KEY | KEY |
|* 5 | TABLE ACCESS FULL | PRODUCT_VIEWS_P | 1 | 1667 | 1668 | 00:00:00.01 | 7 | KEY | KEY |
----------------------------------------------------------------------------------------------------------------------------
3 - filter(:V_MON<ADD_MONTHS(:V_MON,1))
5 - filter(("VIEW_DATE">=:V_MON AND "VIEW_DATE"<ADD_MONTHS(:V_MON,1)))