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

고객 월 실적 마감 배치가 고객 2,000명마다 주문 테이블을 세 번 뒤진다

중급INSERT한빛유통245 / 700

상황 ​

매월 1일 새벽 정산 마감 배치의 한 단계는 지난달 고객별 주문 건수, 주문 금액, 마지막 주문 일시를 고객 월 실적 테이블에 넣는다. 강도현이 남긴 인수인계 메모에는 "이 단계 0.7초, 고객 수가 늘면 같이 늘어남"이라고 적혀 있다. 오과장은 내년에 제휴사 고객을 받아 고객 수가 열 배로 는다며 미리 봐 달라고 했다.

테이블과 인덱스 ​

  • M_CUS: 2,000행 · 30블록 · PK_M_CUS (CUS_ID)
  • T_ORD: 100,000행 · 896블록 · 고객마다 50건(2,000행 간격으로 흩어져 있다) · 2024년 6월 4,110건(고객마다 2 ~ 3건) · PK_T_ORD (ORD_SEQ) · IX_ORD_CUS (CUS_ID)
  • T_CUS_MON 고객 월 실적: 이 배치가 채우는 집계 테이블(BASE_YM, CUS_ID, ORD_CNT, ORD_AMT, LAST_ORD_DT) · 인덱스 없음
  • 운영 DB는 2021년 결과 오류 버그를 피하려고 _optimizer_unnest_scalar_sq = FALSE를 시스템 수준에 걸어 두었다(스칼라 서브쿼리 Unnesting을 하지 않는다)
  • 통계 최신

원본 SQL ​

sql
INSERT INTO T_CUS_MON (BASE_YM, CUS_ID, ORD_CNT, ORD_AMT, LAST_ORD_DT)
SELECT '202406', c.CUS_ID,
       (SELECT COUNT(*)
          FROM T_ORD o
         WHERE o.CUS_ID = c.CUS_ID
           AND o.ORD_DT >= DATE '2024-06-01' AND o.ORD_DT < DATE '2024-07-01'),
       (SELECT NVL(SUM(o.ORD_AMT), 0)
          FROM T_ORD o
         WHERE o.CUS_ID = c.CUS_ID
           AND o.ORD_DT >= DATE '2024-06-01' AND o.ORD_DT < DATE '2024-07-01'),
       (SELECT MAX(o.ORD_DT)
          FROM T_ORD o
         WHERE o.CUS_ID = c.CUS_ID
           AND o.ORD_DT >= DATE '2024-06-01' AND o.ORD_DT < DATE '2024-07-01')
  FROM M_CUS c

실행계획 학습용 예시 ​

xplan
----------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT                      |            |      1 |        |      0 |00:00:00.71 |     316K|
|   1 |  LOAD TABLE CONVENTIONAL              | T_CUS_MON  |      1 |        |      0 |00:00:00.71 |     316K|
|   2 |   SORT AGGREGATE                      |            |   2000 |      1 |   2000 |00:00:00.22 |     104K|
|*  3 |    TABLE ACCESS BY INDEX ROWID BATCHED| T_ORD      |   2000 |      2 |   4110 |00:00:00.21 |     104K|
|*  4 |     INDEX RANGE SCAN                  | IX_ORD_CUS |   2000 |     50 |    100K|00:00:00.03 |    4612 |
|   5 |   SORT AGGREGATE                      |            |   2000 |      1 |   2000 |00:00:00.22 |     104K|
|*  6 |    TABLE ACCESS BY INDEX ROWID BATCHED| T_ORD      |   2000 |      2 |   4110 |00:00:00.21 |     104K|
|*  7 |     INDEX RANGE SCAN                  | IX_ORD_CUS |   2000 |     50 |    100K|00:00:00.03 |    4612 |
|   8 |   SORT AGGREGATE                      |            |   2000 |      1 |   2000 |00:00:00.22 |     104K|
|*  9 |    TABLE ACCESS BY INDEX ROWID BATCHED| T_ORD      |   2000 |      2 |   4110 |00:00:00.21 |     104K|
|* 10 |     INDEX RANGE SCAN                  | IX_ORD_CUS |   2000 |     50 |    100K|00:00:00.03 |    4612 |
|  11 |   TABLE ACCESS FULL                   | M_CUS      |      1 |   2000 |   2000 |00:00:00.01 |      30 |
----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   3 - filter(("O"."ORD_DT">=TO_DATE(' 2024-06-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND
              "O"."ORD_DT"<TO_DATE(' 2024-07-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')))
   4 - access("O"."CUS_ID"=:B1)
   (6 · 7 · 9 · 10 도 같은 모양)

(DBMS_XPLAN 이 줄여 쓴 값 — 104K = 104,612 · 316K = 315,962)

문제 ​

  1. 이 단계의 일량이 어디서 나오는지 Starts와 Buffers로 설명하시오. 고객 수가 열 배가 되면 어떻게 되는지도 쓰시오.
  2. 같은 결과가 들어가도록 INSERT 문을 고치시오. 그 달에 주문이 없는 고객의 값이 원래와 같아야 한다는 점에 주의하시오.
  3. 파라미터 _optimizer_unnest_scalar_sq를 기본값으로 되돌리는 안과 SQL을 고치는 안을 비교하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자