Skip to content
튜닝 연습Lock과 동시성 제어
LOCK-042

메인 화면의 인기 상품 칸이 가끔 비고, 제휴사 상품 피드는 가끔 ORA-08103으로 끊긴다

기초PLSQL이커머스537 / 700

이 문제는 교재 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_id
  • 메인 화면은 product_rank에서 순위 상위 20개를 초당 약 40번 읽는다
  • 상품 피드는 product_rank를 처음부터 차례로 읽으며(정렬 없음, 순위는 칸으로 보낸다) 행마다 products를 PK로 찾아 붙인다. 20행씩 가져와 제휴사 API로 보내고 응답을 받은 뒤 다음 20행을 가져온다

원본 SQL ​

sql
-- 순위 갱신 잡 (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;

실행계획 학습용 예시 ​

xplan
[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 할 때마다 바뀐다)

문제 ​

  1. 메인 화면이 0건을 받은 이유와 피드가 ORA-08103으로 끊긴 이유를 TRUNCATE가 어떤 문장인지와 이어서 설명하시오. 피드와 메인 화면이 읽는 중인데도 TRUNCATE가 기다리지 않은 이유도 쓰시오.
  2. 순위를 갱신하는 동안에도 화면과 피드가 이전 순위를 끝까지 읽도록 잡을 고치시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자