Skip to content
튜닝 연습PL/SQL과 사용자 정의 함수
PLSQL-021

recursive calls는 4,162에서 19가 됐는데 정산 수수료 반영은 여전히 1.4초다

중급PLSQL한빛유통671 / 700

상황 ​

매월 1일 「정산 수수료 반영」 단계는 지난달 정산마다 결제 수단과 고객 등급으로 PG 수수료를 계산해 정산 행에 적는다. 수수료 규칙은 결제 화면과 같은 패키지 PKG_PG_FEE를 쓴다. 지난주 코드 리뷰에서 "행마다 UPDATE"를 지적받은 개발자가 BULK COLLECT와 FORALL로 바꿨다. 그런데 걸리는 시간은 1.41초에서 1.37초가 됐을 뿐이다. 개발자가 물었다. "recursive calls는 4,162에서 19로 줄었는데 왜 그대로죠?"

테이블과 인덱스 ​

  • T_ORD_STLM 정산(이 문제용으로 FEE_AMT 컬럼을 더한 판): 100,000행 · 640블록 · 2024년 11월 정산 4,110건 · 인덱스 없음
  • 정산 배치(P1)가 매번 TRUNCATE하고 다시 적재하는 테이블이라, 적재 비용 때문에 인덱스를 두지 않았다(교재 14화의 결정).
  • T_ORD: 100,000행 · 896블록 · PK_T_ORD (ORD_SEQ) · PAY_TP CARD · CASH · POINT
  • M_CUS: 2,000행 · 30블록 · PK_M_CUS (CUS_ID) · CUS_GD
  • PKG_PG_FEE.CALC(pay_tp, cus_gd, amt): SQL 없는 PL/SQL 함수 · 이 단계가 도는 동안 다른 세션은 T_ORD_STLM을 고치지 않는다

원본 SQL ​

sql
PROCEDURE APPLY_PG_FEE(p_ym VARCHAR2) IS              -- p_ym = '202411'
  CURSOR c IS
    SELECT s.STLM_SEQ, s.STLM_AMT, o.PAY_TP, m.CUS_GD
      FROM T_ORD_STLM s, T_ORD o, M_CUS m
     WHERE s.STLM_DT >= TO_DATE(p_ym, 'YYYYMM')
       AND s.STLM_DT <  ADD_MONTHS(TO_DATE(p_ym, 'YYYYMM'), 1)
       AND o.ORD_SEQ = s.ORD_SEQ
       AND m.CUS_ID  = s.CUS_ID;
  TYPE t_num IS TABLE OF NUMBER       INDEX BY PLS_INTEGER;
  TYPE t_vc  IS TABLE OF VARCHAR2(40) INDEX BY PLS_INTEGER;
  v_seq t_num;  v_amt t_num;  v_tp t_vc;  v_gd t_vc;  v_fee t_num;
BEGIN
  OPEN c;
  LOOP
    FETCH c BULK COLLECT INTO v_seq, v_amt, v_tp, v_gd LIMIT 1000;
    EXIT WHEN v_seq.COUNT = 0;
    FOR i IN 1 .. v_seq.COUNT LOOP
      v_fee(i) := PKG_PG_FEE.CALC(v_tp(i), v_gd(i), v_amt(i));
    END LOOP;
    FORALL i IN 1 .. v_seq.COUNT
      UPDATE T_ORD_STLM
         SET FEE_AMT = v_fee(i)
       WHERE STLM_SEQ = v_seq(i);
  END LOOP;
  CLOSE c;
  COMMIT;
END;

실행계획 학습용 예시 ​

xplan
[두 판 비교 — 2024년 11월 정산 4,110건 (V$MYSTAT 증가분)]
  판                                       경과     recursive calls   UPDATE Executions   session logical reads
  지난주 판 (커서 FOR 루프 · 행마다 UPDATE)  1.41 초         4,162               4,110             2,636,178
  지금 판 (BULK 1000 · FORALL UPDATE)        1.37 초            19                   5             2,636,141

[V$SQL — 지금 판의 UPDATE]
  SQL_TEXT                                                  EXECUTIONS  ROWS_PROCESSED  BUFFER_GETS
  UPDATE T_ORD_STLM SET FEE_AMT = :B1 WHERE STLM_SEQ = :B2           5           4,110    2,634,510

[그 UPDATE의 원소 하나 — 실행계획]
---------------------------------------------------------------------------------------
| Id  | Operation          | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------
|   0 | UPDATE STATEMENT   |            |      1 |        |      0 |00:00:00.01 |     641 |
|   1 |  UPDATE            | T_ORD_STLM |      1 |        |      0 |00:00:00.01 |     641 |
|*  2 |   TABLE ACCESS FULL| T_ORD_STLM |      1 |      1 |      1 |00:00:00.01 |     640 |
---------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("STLM_SEQ"=:B1)

문제 ​

  1. recursive calls는 4,162 → 19로 줄었는데 시간과 논리 읽기는 그대로인 이유를 V$SQL과 실행계획으로 설명하시오.
  2. 정산 적재(P1)의 비용을 늘리지 않으면서 수수료 반영을 고치시오. 고친 방법의 전제 조건도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자