Skip to content
튜닝 연습파싱과 바인드 변수
PARSE-017

카테고리 목록 API가 몰리는 시간마다 row cache lock이 1위로 오르고, 가끔 주문이 반쯤 남는다

실전DDL이커머스562 / 700

이 문제는 교재 18화 졸업 과제와 같은 이커머스 데이터베이스를 씁니다. 『튜너의 1년』을 순서대로 읽고 있다면 졸업 과제를 먼저 풀고 오세요. 아래 「테이블과 인덱스」 칸에 과제의 답 일부(제약 조건, 인덱스, 통계의 실제 상태)가 나옵니다.

상황 ​

이커머스의 「카테고리 상품 목록」 API는 MySQL 시절의 프로시저를 옮겨 온 것이다. 요청마다 고른 카테고리의 하위 카테고리를 임시 테이블로 만들고, 상품과 조인한 뒤 그 테이블을 지운다. 세일 기간에는 초당 40번쯤 호출된다. 같은 기간 고객센터에는 "결제는 안 했는데 주문서가 남아 있다"는 문의가 하루 몇 건씩 들어온다. 주문 화면은 주문을 저장하는 도중에 프로모션 대상인지 확인하려고 같은 로직을 부른다.

테이블과 인덱스 ​

  • categories: 53행 · 4블록 · parent_id로 이어진 계층 · PK(category_id)만 있다
  • products: 280행 · 8블록 · PK(product_id)만 있다 · 상품은 카테고리마다 5개 남짓
  • orders: 34,908행 · 193블록 · 주문 화면은 orders에 한 행을 넣은 뒤 결제가 끝나면 커밋하고, 실패하면 ROLLBACK한다.
  • 임시 테이블 이름에는 API 서버가 매기는 요청 번호가 붙는다. CURSOR_SHARING = EXACT

원본 SQL ​

sql
-- 요청 하나 (요청 번호 48213 · 카테고리 12)
CREATE TABLE TMP_CAT_48213 AS
  SELECT category_id
    FROM categories
   START WITH category_id = 12
 CONNECT BY PRIOR category_id = parent_id;

SELECT p.product_id, p.name
  FROM products p, TMP_CAT_48213 t
 WHERE p.category_id = t.category_id
 ORDER BY p.name;

DROP TABLE TMP_CAT_48213 PURGE;

실행계획 학습용 예시 ​

xplan
[AWR · 세일 첫날 20:00 ~ 21:00 · Load Profile (초당)]
  DB CPU                        1.9
  Executes (SQL)            1,540
  Parses (SQL)                402
  Hard parses (SQL)           131
  Redo size (bytes)     1,184,000

[Top Foreground Events]
  이벤트                              % DB time
  row cache lock                          24.1
  DB CPU                                  21.7
  library cache lock                      13.8
  enq: RO - fast object reuse              9.6
  latch: shared pool                       6.2

[Time Model]
  parse time elapsed                46.3% of DB time
  hard parse elapsed time           44.0% of DB time

[10046 → TKPROF · 요청 하나]
  CREATE TABLE TMP_CAT_48213 AS SELECT …                        Parse 1 · Execute 1 · Misses in library cache during parse: 1
  SELECT p.product_id, p.name FROM products p, TMP_CAT_48213 t …   Parse 1 · Execute 1 · Fetch 1 · Misses in library cache during parse: 1
  DROP TABLE TMP_CAT_48213 PURGE                                Parse 1 · Execute 1 · Misses in library cache during parse: 1
  OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS                   Execute 61 (딕셔너리 OBJ$, TAB$, SEG$, COL$ 등에 INSERT와 DELETE)
  요청 하나 경과 0.21초 (그중 상품을 읽고 내보내는 시간 0.002초)

[주문 화면 · 결제가 실패한 주문 하나의 순서]
  INSERT INTO orders …                   (아직 커밋 전)
  프로모션 확인 → CREATE TABLE TMP_CAT_…
  결제 실패 → ROLLBACK                   → orders 행이 남아 있다

문제 ​

  1. 요청 하나에 0.2초가 걸리고 세일 시간에 row cache lock과 library cache lock이 위로 오르는 이유를 위 자료로 설명하시오. 주문이 반쯤 남는 일과는 어떻게 이어지는지도 쓰시오.
  2. 대안 셋(임시 테이블 이름을 고정한다, GTT를 미리 만들어 둔다, 임시 테이블 없이 한 문장으로 쓴다)을 비교하시오.
  3. 하나를 골라 고치시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자