Skip to content
튜닝 연습조인 튜닝
JOIN-058

가격 조회가 느리다며 상품가격 테이블에 종료일 칸을 추가하자는 변경 요청서가 올라왔다

실전DDL유통208 / 700

상황 ​

본사 레거시 유통의 정보기술팀이 변경 요청서를 올렸다. "상품가격(CF05)은 적용일자만 있어서 판매일의 가격을 구하는 SQL이 복잡하고 느리다. 종료일(END_DT) 칸을 추가해 선분이력으로 바꾸고, 가격 등록 화면이 새 가격을 넣을 때 앞 구간의 종료일을 닫게 하자." 근거로 든 것은 월 1회 도는 「연간 정가 매출」 배치가 6초 넘게 걸린다는 것이다. 선배는 "CF05를 쓰는 SQL 셋과 등록 화면을 다 보고, 요청서를 받을지 SQL에서 풀지 숫자와 대가로 정리해 와"라고 했다.

테이블과 인덱스 ​

  • CF05 상품가격 (점이력): 157행 · 5블록 · CF05_PK (ITM_NO, UPRC_KN_CD, APL_DT) · 리프 블록 하나 · 판매가(UPRC_KN_CD = '10') 127행 = 상품 89개의 '20220101' + 38개 상품의 '20230615' · 나머지 30행은 발주단가
  • CF11 판매상품: 1,096,708행 · 8,204블록 · CF11_PK (SLE_NO, ITM_NO) · SLE_NO 앞 8자리가 판매일자 · 2023년 548,213줄(테이블의 절반)
  • CF05를 쓰는 곳: [Q1] POS 결제 화면의 현재가(하루 3만 번), [Q2] 연간 정가 매출 배치(월 1회), [Q3] 영수증 재출력의 당시 정가(영수증 1장에 1 ~ 4줄, 하루 500번), [Q4] 가격 등록 화면(가격 개정 때, 1년에 몇 번)
  • 통계 최신

원본 SQL ​

sql
-- [Q1] POS 현재가
SELECT ITM_UPRC
  FROM CF05
 WHERE ITM_NO = :itm AND UPRC_KN_CD = '10'
   AND APL_DT = (SELECT MAX(APL_DT) FROM CF05
                  WHERE ITM_NO = :itm AND UPRC_KN_CD = '10' AND APL_DT <= :today);

-- [Q2] 연간 정가 매출 (월 · 상품별)
SELECT SUBSTR(d.SLE_NO, 1, 6) AS ym, d.ITM_NO,
       SUM(d.SLE_QTY * (SELECT p.ITM_UPRC
                          FROM CF05 p
                         WHERE p.ITM_NO = d.ITM_NO AND p.UPRC_KN_CD = '10'
                           AND p.APL_DT = (SELECT MAX(x.APL_DT) FROM CF05 x
                                            WHERE x.ITM_NO = d.ITM_NO AND x.UPRC_KN_CD = '10'
                                              AND x.APL_DT <= SUBSTR(d.SLE_NO, 1, 8)))) AS list_amt
  FROM CF11 d
 WHERE d.SLE_NO >= '20230101' AND d.SLE_NO < '20240101'
 GROUP BY SUBSTR(d.SLE_NO, 1, 6), d.ITM_NO;

-- [Q3] 영수증 재출력: Q2 와 같은 서브쿼리를 영수증 한 장(1 ~ 4줄)에 쓴다
-- [Q4] 가격 등록: INSERT INTO CF05 (ITM_NO, UPRC_KN_CD, APL_DT, ITM_UPRC) VALUES (...);

-- 변경 요청서의 DDL
ALTER TABLE CF05 ADD (END_DT VARCHAR2(8) DEFAULT '99991231');
CREATE INDEX CF05_X01 ON CF05 (ITM_NO, UPRC_KN_CD, END_DT);

실행계획 학습용 예시 ​

xplan
[Q1] Buffers 3 (INDEX RANGE SCAN (MIN/MAX) 1 + INDEX UNIQUE SCAN 1 + 테이블 1)
[Q3] 영수증 한 장(4줄) Buffers 12 + 판매상품 4

[Q2]
------------------------------------------------------------------------------------------------------------
| Id  | Operation                       | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                |         |      1 |        |   1056 |00:00:06.21 |    1653K|
|   1 |  TABLE ACCESS BY INDEX ROWID    | CF05    |    548K|      1 |    548K|00:00:03.10 |    1644K|
|*  2 |   INDEX UNIQUE SCAN             | CF05_PK |    548K|      1 |    548K|00:00:02.31 |    1096K|
|   3 |    SORT AGGREGATE               |         |    548K|      1 |    548K|00:00:01.12 |     548K|
|   4 |     FIRST ROW                   |         |    548K|      1 |    548K|00:00:00.93 |     548K|
|*  5 |      INDEX RANGE SCAN (MIN/MAX) | CF05_PK |    548K|      1 |    548K|00:00:00.81 |     548K|
|   6 |  HASH GROUP BY                  |         |      1 |   1056 |   1056 |00:00:06.21 |    1653K|
|*  7 |   TABLE ACCESS FULL             | CF11    |      1 |    548K|    548K|00:00:00.09 |    8204 |
------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("P"."ITM_NO"=:B1 AND "P"."UPRC_KN_CD"='10' AND "P"."APL_DT"=(SELECT MAX("X"."APL_DT") FROM "CF05" "X" ...))
   5 - access("X"."ITM_NO"=:B1 AND "X"."UPRC_KN_CD"='10' AND "X"."APL_DT"<=SUBSTR(:B2,1,8))
   7 - filter(("D"."SLE_NO">='20230101' AND "D"."SLE_NO"<'20240101'))

문제 ​

  1. Q2가 느린 까닭을 1번 ~ 5번 줄의 Starts로 설명하시오. 스칼라 서브쿼리 캐시가 왜 도움이 되지 않았는지, END_DT가 생기면 이 일량이 줄어드는지도 쓰시오.
  2. 요청서를 받았을 때 Q1과 Q3이 얼마나 나아지는지, Q4와 데이터 정합성에는 어떤 대가가 생기는지 숫자와 함께 쓰시오.
  3. 테이블 구조를 바꾸지 않고 같은 단순함을 얻는 방법을 DDL과 고친 Q2로 작성하고, 요청서에 대한 결론을 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자