테마
이 문제는 교재 18화 졸업 과제와 같은 이커머스 데이터베이스를 씁니다. 『튜너의 1년』을 순서대로 읽고 있다면 졸업 과제를 먼저 풀고 오세요. 아래 「테이블과 인덱스」 칸에 과제의 답 일부(제약 조건, 인덱스, 통계의 실제 상태)가 나옵니다.
이커머스 메인 화면의 「인기 상품」 칸은 순위 테이블 product_rank를 읽는다. 순위는 잡이 10분마다 지우고 다시 채운다. 고객센터에는 "인기 상품 칸이 텅 비어 있다"는 캡처가 하루 몇 건씩 들어오는데, 새로 고치면 다시 나온다.
같은 테이블을 읽어 제휴 가격비교 사이트로 보내는 「상품 피드」는 한 번에 2분쯤 걸리고, 하루 스물네 번 가운데 서너 번은 ORA-08103: object no longer exists로 끊긴다. 피드 담당자는 테이블을 지운 적이 없다고 한다.
product_rank 인기 상품 순위(문제 설정): 약 280행 · 3블록 · 상품마다 한 행(product_id · view_cnt · avg_rating · rnk) · 인덱스 없음product_views: 100,000행 · 367블록 / reviews: 15,000행 · 775블록 / products: 280행 · 8블록 · PK product_idproduct_rank에서 순위 상위 20개를 초당 약 40번 읽는다product_rank를 처음부터 차례로 읽으며(정렬 없음, 순위는 칸으로 보낸다) 행마다 products를 PK로 찾아 붙인다. 20행씩 가져와 제휴사 API로 보내고 응답을 받은 뒤 다음 20행을 가져온다-- 순위 갱신 잡 (10분마다)
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE product_rank';
INSERT INTO product_rank (product_id, view_cnt, avg_rating, rnk)
SELECT v.product_id, v.cnt, r.avg_rating, RANK() OVER (ORDER BY v.cnt DESC)
FROM (SELECT product_id, COUNT(*) AS cnt
FROM product_views
WHERE view_date >= SYSDATE - 7
GROUP BY product_id) v
LEFT JOIN (SELECT product_id, ROUND(AVG(rating), 1) AS avg_rating
FROM reviews
GROUP BY product_id) r
ON r.product_id = v.product_id;
COMMIT;
END;
-- 상품 피드 (참고)
SELECT k.product_id, p.name, k.rnk, k.avg_rating
FROM product_rank k JOIN products p ON p.product_id = k.product_id;
[10:20 전후 — 잡과 화면과 피드]
시각 순위 갱신 잡 (SID 301) 메인 화면 (초당 40번) 상품 피드 (SID 288)
10:19:10 피드 시작 · 20행씩 가져와 보낸다
10:20:00.00 TRUNCATE (0.02초 · 기다림 없음)
10:20:00.02 INSERT … SELECT (0.61초) 0건 · 0건 · … (24번)
10:20:00.63 COMMIT 다시 20건
10:20:41 다음 20행 → ORA-08103: object no longer exists
(보낸 120행 · 못 보낸 160행)
[메인 화면 SQL — 10:20:00.30 의 한 번 (ALLSTATS LAST)]
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 0 |00:00:00.01 | 3 |
|* 1 | COUNT STOPKEY | | 1 | | 0 |00:00:00.01 | 3 |
| 2 | VIEW | | 1 | 280 | 0 |00:00:00.01 | 3 |
|* 3 | SORT ORDER BY STOPKEY| | 1 | 280 | 0 |00:00:00.01 | 3 |
| 4 | TABLE ACCESS FULL | PRODUCT_RANK | 1 | 280 | 0 |00:00:00.01 | 3 |
---------------------------------------------------------------------------------------------
1 - filter(ROWNUM<=20)
3 - filter(ROWNUM<=20)
[V$LOCK — 10:20:00.30 · product_rank]
SID TYPE LMODE REQUEST
301 TM 3 0 (INSERT 중 · TRUNCATE 하는 순간에는 6 을 잠깐 쥐었다)
(메인 화면 · 피드 세션은 이 테이블에 쥔 락이 없다)
[USER_OBJECTS — product_rank 의 DATA_OBJECT_ID]
10:19 → 88214 · 10:21 → 88231 (TRUNCATE 할 때마다 바뀐다)
TRUNCATE가 어떤 문장인지와 이어서 설명하시오. 피드와 메인 화면이 읽는 중인데도 TRUNCATE가 기다리지 않은 이유도 쓰시오.