Skip to content
튜닝 연습소트 튜닝과 페이징
SORT-048

월 마감 요약 세 개의 합계가 서로 어긋나고, 마감 배치는 판매상품 테이블을 세 번 통째로 읽는다

중급INSERT유통353 / 700

상황 ​

유통 본사 월 마감 배치는 매월 1일 새벽에 지난달 판매를 요약 테이블 세 개에 넣는다. 매장 월 매출(CF_SHP_MON_SLS), 상품 월 판매(CF_ITM_MON_SLS), 요일 월 매출(CF_DOW_MON_SLS)이다. 세 문장은 묶는 기준만 다르고 모두 같은 달의 CF10과 CF11을 조인한다. 배치 체인의 단계 3, 5, 7이라 사이에 다른 단계가 끼어 02시 05분, 21분, 38분에 따로 돈다. 재무팀이 6월 마감을 보다가 매장 월 매출의 판매 건수 합계(20,550건)와 요일 월 매출의 판매 건수 합계(20,553건)가 다르다고 알려 왔다. 매장 두 곳의 POS는 밤에 꺼져 있다가 새벽에 켜지면서 지난 판매를 몰아서 올린다. 배치 담당 오과장은 "세 문장을 하나로 줄일 수 있으면 숫자도 시간도 같이 해결되지 않겠나"라고 묻는다.

테이블과 인덱스 ​

  • CF10 판매: 500,000행 · 4,583블록 · CF10_PK (SLE_NO) · SLE_DT VARCHAR2(8) · SLE_DOW 요일('1' ~ '7') · SHP_NO 매장 6곳. 2023년 6월 판매는 20,550건이고 SLE_DT로 시작하는 인덱스는 없다.
  • CF11 판매상품: 1,096,708행 · 8,204블록 · CF11_PK (SLE_NO, ITM_NO) · 6월 판매상품은 45,210줄(상품 88개)이다.
  • 요약 테이블 셋은 배치가 넣기만 한다(인덱스는 PK뿐). 판매 건수는 판매상품과 조인한 뒤에 세므로 COUNT(DISTINCT a.SLE_NO)로 센다.
  • 통계는 최신이다. 이 배치는 직접 경로 적재를 쓰지 않는다.

원본 SQL ​

sql
-- 월 마감 · :ym = '202306'
-- [단계 3 · 02:05] 매장 월 매출
INSERT INTO CF_SHP_MON_SLS (SLE_YM, SHP_NO, SLE_CNT, SLE_AMT)
SELECT :ym, a.SHP_NO, COUNT(DISTINCT a.SLE_NO), SUM(b.SLE_QTY * b.SLE_UPRC)
  FROM CF10 a, CF11 b
 WHERE a.SLE_DT LIKE :ym || '%'
   AND b.SLE_NO = a.SLE_NO
 GROUP BY a.SHP_NO;
COMMIT;

-- [단계 5 · 02:21] 상품 월 판매
INSERT INTO CF_ITM_MON_SLS (SLE_YM, ITM_NO, SLE_QTY, SLE_AMT)
SELECT :ym, b.ITM_NO, SUM(b.SLE_QTY), SUM(b.SLE_QTY * b.SLE_UPRC)
  FROM CF10 a, CF11 b
 WHERE a.SLE_DT LIKE :ym || '%'
   AND b.SLE_NO = a.SLE_NO
 GROUP BY b.ITM_NO;
COMMIT;

-- [단계 7 · 02:38] 요일 월 매출
INSERT INTO CF_DOW_MON_SLS (SLE_YM, SLE_DOW, SLE_CNT, SLE_AMT)
SELECT :ym, a.SLE_DOW, COUNT(DISTINCT a.SLE_NO), SUM(b.SLE_QTY * b.SLE_UPRC)
  FROM CF10 a, CF11 b
 WHERE a.SLE_DT LIKE :ym || '%'
   AND b.SLE_NO = a.SLE_NO
 GROUP BY a.SLE_DOW;
COMMIT;

실행계획 학습용 예시 ​

xplan
[단계 3 · 매장 월 매출 (단계 5는 2번 줄이 HASH GROUP BY, 단계 7은 같은 모양)]
--------------------------------------------------------------------------------------------------------------
| Id  | Operation                | Name           | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
--------------------------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT         |                |      1 |        |      0 |00:00:00.91 |   12803 |          |
|   1 |  LOAD TABLE CONVENTIONAL | CF_SHP_MON_SLS |      1 |        |      0 |00:00:00.91 |   12803 |          |
|   2 |   SORT GROUP BY          |                |      1 |      6 |      6 |00:00:00.91 |   12787 | 2154K (0)|
|*  3 |    HASH JOIN             |                |      1 |  45724 |  45210 |00:00:00.84 |   12787 | 1972K (0)|
|*  4 |     TABLE ACCESS FULL    | CF10           |      1 |  20786 |  20550 |00:00:00.04 |    4583 |          |
|   5 |     TABLE ACCESS FULL    | CF11           |      1 |   1096K|   1096K|00:00:00.12 |    8204 |          |
--------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("B"."SLE_NO"="A"."SLE_NO")
   4 - filter("A"."SLE_DT" LIKE :YM||'%')

[세 단계]  단계 3 · 12,803   단계 5 · 12,881   단계 7 · 12,804   합계 Buffers 38,488 · 경과 2.7초
[대조 · 6월]  단계 3 판매 건수 합 20,550 · 단계 7 판매 건수 합 20,553 · 세 요약의 금액 합이 모두 다르다

문제 ​

  1. 세 요약의 합계가 서로 어긋나는 까닭과, 배치가 읽는 양이 어디서 세 배가 되는지 설명하시오.
  2. 같은 달을 한 번만 조인해 세 요약을 한 문장으로 채우도록 고치시오. 어느 줄을 어느 테이블에 넣을지 무엇으로 가르는지도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자