Skip to content
튜닝 연습서브쿼리와 쿼리 변환
SUBQ-035

부분 취소 재정산을 연 뒤로 월초 정산 보정 배치가 0.1초에서 12초가 됐다

중급INSERT한빛유통270 / 700

상황 ​

매달 1일 03시에는 「정산 보정」 배치가 돈다. 지난달 결제 완료 주문 가운데 정산 테이블에 (주문번호, 정산일) 행이 없는 주문을 찾아 정산 행을 넣는 단계로, 밤마다 도는 정산 적재가 장애로 빠뜨린 주문을 메운다. 예전에는 조건이 ORD_SEQ NOT IN (…) 한 컬럼이었고 0.1초 안팎에 끝났다. 6월에 「부분 취소 재정산」을 열면서 같은 주문이 날짜를 달리해 두 번 정산될 수 있게 되자, 조민우가 조건을 두 컬럼 (ORD_SEQ, PAY_DT) NOT IN (…)으로 바꿨다. 7월 1일 보정 단계가 12초 걸렸다. 오과장은 석 달치를 한 번에 보정하는 분기 말에는 40초를 넘길 것이라고 걱정한다.

테이블과 인덱스 ​

  • T_ORD: 100,000행 · 896블록 · PK_T_ORD (ORD_SEQ) · PAY_DT DATE(자정) · 2024년 6월 결제 4,110건 중 COMPLETE 3,699건
  • T_ORD_STLM: 100,000행 · 640블록 · 인덱스 없음 · 모든 컬럼이 NULL 허용(제약 없음)이지만 지금 NULL은 0건(NUM_NULLS 0) · STLM_DT에는 결제일(자정)을 넣는다 · 행은 ORD_SEQ 순으로 쌓여 있다
  • 6월 COMPLETE 주문 중 정산 행이 없는 주문은 23건이다 · 시퀀스 SQ_STLM · 통계 최신
  • 바인드 :v_from = DATE '2024-06-01', :v_to = DATE '2024-07-01'

원본 SQL ​

sql
INSERT INTO T_ORD_STLM (STLM_SEQ, ORD_SEQ, CUS_ID, STLM_DT, STLM_AMT)
SELECT SQ_STLM.NEXTVAL, o.ORD_SEQ, o.CUS_ID, o.PAY_DT, o.PAY_AMT
  FROM T_ORD o
 WHERE o.PAY_DT >= :v_from
   AND o.PAY_DT <  :v_to
   AND o.ORD_ST = 'COMPLETE'
   AND (o.ORD_SEQ, o.PAY_DT) NOT IN (SELECT s.ORD_SEQ, s.STLM_DT
                                       FROM T_ORD_STLM s)

실행계획 학습용 예시 · 주문 건수는 적재 데이터 그대로 ​

xplan
------------------------------------------------------------------------------------------------
| Id  | Operation                | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT         |            |      1 |        |      0 |00:00:12.43 |    1192K|
|   1 |  LOAD TABLE CONVENTIONAL | T_ORD_STLM |      1 |        |      0 |00:00:12.43 |    1192K|
|   2 |   SEQUENCE               | SQ_STLM    |      1 |        |     23 |00:00:12.42 |    1191K|
|*  3 |    FILTER                |            |      1 |        |     23 |00:00:12.42 |    1191K|
|*  4 |     TABLE ACCESS FULL    | T_ORD      |      1 |   3699 |   3699 |00:00:00.02 |     896 |
|*  5 |     TABLE ACCESS FULL    | T_ORD_STLM |   3699 |      1 |   3676 |00:00:12.38 |    1190K|
------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   3 - filter( NOT EXISTS (SELECT 0 FROM "T_ORD_STLM" "S" WHERE LNNVL("S"."ORD_SEQ"<>:B1) AND
              LNNVL("S"."STLM_DT"<>:B2)))
   4 - filter(("O"."ORD_ST"='COMPLETE' AND "O"."PAY_DT">=:V_FROM AND "O"."PAY_DT"<:V_TO))
   5 - filter((LNNVL("S"."ORD_SEQ"<>:B1) AND LNNVL("S"."STLM_DT"<>:B2)))

[6월까지 · 한 컬럼 ORD_SEQ NOT IN]  HASH JOIN ANTI NA · Buffers 1,562 · 0.07초

문제 ​

  1. 12초가 어디서 나오는지 실행계획 3번과 5번 줄의 Starts와 Buffers로 설명하시오. 한 컬럼일 때는 왜 빨랐는지도 설명하시오.
  2. 같은 결과를 내면서 안티 조인으로 처리되도록 SQL을 고치고, 고친 SQL이 원래 SQL과 같은 답을 내는 조건을 쓰시오.
  3. 같은 일이 다시 생기지 않게 테이블 쪽에서 할 수 있는 일을 그 대가와 함께 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자