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

혼자 하면 0.1초인 전표 확정이 월말 오후에는 2초씩 걸린다

중급PLSQL한빛유통503 / 700

상황 ​

한빛유통 정산팀은 매달 말일 오후에 고객별 정산을 「전표 확정」 화면으로 확정한다. 담당자 열두 명이 두 시간 동안 약 1,800건을 확정한다. 회계 규정 제14조는 이렇다. "전표번호는 1번부터 결번 없이 이어져야 한다. 결번은 허용하지 않는다." 그래서 전표번호는 시퀀스가 아니라 채번 테이블로 매긴다. 혼자 테스트하면 확정 한 건은 0.12초인데, 월말 오후에는 한 건에 평균 1.9초가 걸린다. 오과장은 "서버가 바빠서 그렇다"라고 했다.

테이블과 인덱스 ​

  • T_STLM_NUM 채번: PK (NUM_TP) · 이 문제에서는 전표번호 행 NUM_TP = 'SLIP'을 쓴다
  • T_ORD_STLM 정산: 100,000행 · 640블록 · 이 문제에서는 전표번호 컬럼 SLIP_NO를 더했다 · 인덱스 IX_T_ORD_STLM_CUS (CUS_ID, STLM_DT)
  • T_STLM_SLIP 전표(이 문제를 위해 더한 테이블): 한 달 약 1,800행 · PK (SLIP_NO)
  • T_ORD_BIG: 1,000,000행 · 5,826블록 · 확정할 때 주문 원장과 금액을 대어 본다 · CUS_ID로 찾는 인덱스는 없다 · 고객 한 명의 한 달 주문은 약 14건

원본 SQL ​

sql
PROCEDURE CONFIRM_SLIP(p_cus_id VARCHAR2, p_ym VARCHAR2) IS
  v_no   NUMBER;
  v_amt  NUMBER;
  v_from DATE := TO_DATE(p_ym, 'YYYYMM');
BEGIN
  -- ① 전표번호부터 받는다
  SELECT LAST_NO + 1 INTO v_no FROM T_STLM_NUM WHERE NUM_TP = 'SLIP' FOR UPDATE;
  UPDATE T_STLM_NUM SET LAST_NO = v_no WHERE NUM_TP = 'SLIP';

  -- ② 주문 원장과 금액을 대어 본다
  SELECT SUM(ORD_AMT) INTO v_amt
    FROM T_ORD_BIG
   WHERE CUS_ID = p_cus_id
     AND ORD_DT >= v_from AND ORD_DT < ADD_MONTHS(v_from, 1);

  -- ③ 정산 행에 전표번호를 적고 전표를 만든다
  UPDATE T_ORD_STLM SET SLIP_NO = v_no
   WHERE CUS_ID = p_cus_id AND STLM_DT >= v_from AND STLM_DT < ADD_MONTHS(v_from, 1);
  INSERT INTO T_STLM_SLIP (SLIP_NO, CUS_ID, SLIP_YM, SLIP_AMT, REG_DT)
  VALUES (v_no, p_cus_id, p_ym, v_amt, SYSDATE);

  COMMIT;
END;

실행계획 학습용 예시 ​

xplan
[혼자 한 건 — 단계별 경과]
  ① 번호 받기     0.001초 · Buffers 5 (INDEX UNIQUE SCAN)
  ② 원장 대조     0.11초  · Buffers 5,826 (TABLE ACCESS FULL T_ORD_BIG)
  ③ 전표 쓰기     0.004초 · Buffers 12
  COMMIT          0.003초
  ① 에서 COMMIT 까지 채번 행을 쥐는 시간 약 0.12초

[② 원장 대조 — ALLSTATS LAST]
-------------------------------------------------------------------------------------------
| Id  | Operation          | Name      | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |           |      1 |        |      1 |00:00:00.11 |    5826 |
|   1 |  SORT AGGREGATE    |           |      1 |      1 |      1 |00:00:00.11 |    5826 |
|*  2 |   TABLE ACCESS FULL| T_ORD_BIG |      1 |     14 |     14 |00:00:00.11 |    5826 |
-------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter(("CUS_ID"=:B3 AND "ORD_DT">=:B2 AND "ORD_DT"<ADD_MONTHS(:B2,1)))

[월말 오후 — 열두 명 · 14:00 ~ 16:00]
  확정 1,804건 · 한 건 평균 1.9초 (그중 기다림 1.6초)
  enq: TX - row lock contention 1,690번 · 평균 1.7초 · 대상 T_STLM_NUM ('SLIP' 행)

[V$SESSION — 14:37:12]
SID   EVENT                            SECS  BLOCKING_SESSION  하는 문장
121   db file scattered read              0                    SELECT SUM(ORD_AMT) … T_ORD_BIG …
140   enq: TX - row lock contention       1  121               SELECT LAST_NO + 1 … FOR UPDATE
152   enq: TX - row lock contention       1  121               SELECT LAST_NO + 1 … FOR UPDATE
 …    (아홉 명 더 · 모두 BLOCKING_SESSION 121)

문제 ​

  1. 혼자 0.12초인 확정이 열두 명일 때 1.9초가 되는 이유를 단계별 경과와 대기 이벤트로 설명하시오.
  2. 전표번호에 결번이 생기지 않는다는 규정을 지키면서 기다림을 줄이도록 프로시저를 고치시오. 시퀀스나 자율 트랜잭션 채번으로 바꾸면 안 되는 이유도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자