Skip to content
튜닝 연습파티션
PART-012

파티션 테이블로 옮긴 월 마감 프로시저가 예전보다 빨라지지 않았다

실전PLSQL한빛유통387 / 700

상황 ​

월 마감 프로시저 P_MON_CLOSE는 매월 2일 새벽에 지난달을 마감한다. 월 집계를 쌓고, 미결(PENDING) 주문을 일괄 취소하고, 취소 건수를 점검한다. 원본 T_ORD_BIG에서 월별 파티션 복사본 T_ORD_BIG_P14로 옮기면서 테이블 이름만 바꿨다. 배치 계정은 로그온 트리거로 NLS_DATE_FORMAT = 'YYYY-MM-DD'를 맞춘다. 오과장이 p_ym = '202406'으로 돌린 세 문장의 실행계획을 뽑아 왔다.

테이블과 인덱스 ​

  • T_ORD_BIG_P14 주문(파티션 복사본, 교재 14화): 1,000,000행 · PARTITION BY RANGE (ORD_DT) 월별 36칸과 빈 PMAX, 모두 37칸. P202406은 30번째 칸이고 FULL로 약 161블록, 37칸 전체는 5,868블록이다.
  • ORD_DT는 DATE이고 전부 자정이다. 2024년 6월은 27,390행이고 그중 PENDING이 2,191행, CANCEL이 548행이다.
  • T_MON_SUM 월 집계(문제 설정): 월마다 1행.
  • 인덱스는 없다. ②의 + 0은 원본 T_ORD_BIG에 ORD_DT 인덱스가 있던 2019년에 넣었다(소스 주석).

원본 SQL ​

sql
PROCEDURE P_MON_CLOSE(p_ym IN VARCHAR2) IS                 -- p_ym = '202406'
  v_from   DATE         := TO_DATE(p_ym || '01', 'YYYYMMDD');
  v_to     DATE         := ADD_MONTHS(v_from, 1);
  v_from_s VARCHAR2(10) := SUBSTR(p_ym, 1, 4) || '-' || SUBSTR(p_ym, 5, 2) || '-01';
  v_cnt    NUMBER;
BEGIN
  -- ① 월 집계
  INSERT INTO T_MON_SUM (SUM_MON, ORD_CNT, ORD_AMT)
  SELECT v_from, COUNT(*), SUM(ORD_AMT)
    FROM T_ORD_BIG_P14
   WHERE TO_CHAR(ORD_DT, 'YYYYMM') = p_ym;

  -- ② 미결 주문 일괄 취소 (인덱스 타지 말라고 +0 · 2019.05 강도현)
  UPDATE T_ORD_BIG_P14
     SET ORD_ST = 'CANCEL'
   WHERE ORD_ST = 'PENDING'
     AND ORD_DT + 0 >= v_from
     AND ORD_DT + 0 <  v_to;

  -- ③ 취소 건수 점검
  SELECT COUNT(*) INTO v_cnt
    FROM T_ORD_BIG_P14
   WHERE ORD_ST = 'CANCEL'
     AND ORD_DT >= v_from_s
     AND ORD_DT <  v_to;

  COMMIT;
END;

실행계획 학습용 예시 · 교재 14화 규모 ​

xplan
[① INSERT … SELECT]
----------------------------------------------------------------------------------------------------------------------
| Id  | Operation                | Name          | Starts | E-Rows | A-Rows |      A-Time | Buffers | Pstart | Pstop |
----------------------------------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT         |               |      1 |        |      0 | 00:00:00.12 |    5873 |        |       |
|   1 |  LOAD TABLE CONVENTIONAL | T_MON_SUM     |      1 |        |      0 | 00:00:00.12 |    5873 |        |       |
|   2 |   SORT AGGREGATE         |               |      1 |      1 |      1 | 00:00:00.12 |    5868 |        |       |
|   3 |    PARTITION RANGE ALL   |               |      1 |  10000 |  27390 | 00:00:00.11 |    5868 |      1 |    37 |
|*  4 |     TABLE ACCESS FULL    | T_ORD_BIG_P14 |     36 |  10000 |  27390 | 00:00:00.10 |    5868 |      1 |    37 |
----------------------------------------------------------------------------------------------------------------------

   4 - filter(TO_CHAR(INTERNAL_FUNCTION("ORD_DT"),'YYYYMM')=:B1)

[② UPDATE]
-------------------------------------------------------------------------------------------------------------------
| Id  | Operation             | Name          | Starts | E-Rows | A-Rows |      A-Time | Buffers | Pstart | Pstop |
-------------------------------------------------------------------------------------------------------------------
|   0 | UPDATE STATEMENT      |               |      1 |        |      0 | 00:00:00.16 |    8261 |        |       |
|   1 |  UPDATE               | T_ORD_BIG_P14 |      1 |        |      0 | 00:00:00.16 |    8261 |        |       |
|   2 |   PARTITION RANGE ALL |               |      1 |    200 |   2191 | 00:00:00.11 |    5868 |      1 |    37 |
|*  3 |    TABLE ACCESS FULL  | T_ORD_BIG_P14 |     36 |    200 |   2191 | 00:00:00.11 |    5868 |      1 |    37 |
-------------------------------------------------------------------------------------------------------------------

   3 - filter(("ORD_ST"='PENDING' AND INTERNAL_FUNCTION("ORD_DT")+0>=:B2 AND INTERNAL_FUNCTION("ORD_DT")+0<:B1))

[③ SELECT COUNT(*)]
-------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                   | Name          | Starts | E-Rows | A-Rows |      A-Time | Buffers | Pstart | Pstop |
-------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |               |      1 |        |      1 | 00:00:00.01 |     161 |        |       |
|   1 |  SORT AGGREGATE             |               |      1 |      1 |      1 | 00:00:00.01 |     161 |        |       |
|*  2 |   FILTER                    |               |      1 |        |   2739 | 00:00:00.01 |     161 |        |       |
|   3 |    PARTITION RANGE ITERATOR |               |      1 |    548 |   2739 | 00:00:00.01 |     161 |    KEY |   KEY |
|*  4 |     TABLE ACCESS FULL       | T_ORD_BIG_P14 |      1 |    548 |   2739 | 00:00:00.01 |     161 |    KEY |   KEY |
-------------------------------------------------------------------------------------------------------------------------

   2 - filter(TO_DATE(:B2)<:B1)
   4 - filter(("ORD_ST"='CANCEL' AND "ORD_DT">=TO_DATE(:B2) AND "ORD_DT"<:B1))

[같은 프로시저를 SQL Developer (NLS_DATE_FORMAT = 'DD-MON-RR') 에서 돌리면]
  ③ ORA-01861: literal does not match format string

문제 ​

  1. 세 문장이 각각 몇 칸을 여는지 Pstart, Pstop과 Starts로 쓰고, 칸을 고르거나 고르지 못하는 까닭을 문장마다 설명하시오.
  2. 결과가 바뀌지 않게 프로시저를 고쳐 쓰고, 세 문장의 Buffers가 어떻게 바뀌는지 어림하시오.
  3. ②의 + 0을 지워도 되는지, ③은 칸을 고르는데도 왜 고쳐야 하는지 설명하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자