Skip to content
튜닝 연습인덱스 사용 조건
IDX-074

정보계 리포트 넷 가운데 둘이 일별 상품 판매를 통째로 읽는다며 인덱스 설계를 맡겼다

실전DDL유통74 / 700

상황 ​

유통 정보계의 「일별 상품 판매」(IDX-027의 집계 테이블)는 매장, 일, 상품별 판매를 한 줄씩 담는다. 리포트 넷이 이 테이블을 읽는데, 점장 앱의 R2와 MD의 R3이 느리다. DW팀장은 "PK 순서를 바꿀지, 인덱스를 몇 개 더 만들지 정해 달라. 적재 창은 01시부터 30분이다"라고 했다. 적재는 매일 01시에 전날 약 400행을 넣고, 매월 1일에는 반품 정정을 반영하려고 지난달 약 12,000행을 지우고 다시 넣는다. 그 밖의 DML은 없다.

테이블과 인덱스 ​

  • DW_SLE_DD 일별 상품 판매(이 문제에서 쓰는 파생 테이블): 292,000행 · 1,460블록 · 날짜순 적재 · SLE_YMD NUMBER(8) 20220101 ~ 20231231(730일) · SHP_NO 1001 ~ 1005(5곳) · ITM_NO 88종 · SLE_QTY · SLE_AMT · 하루 약 400행(매장마다 그날 팔린 상품 약 80종)
  • 매장 한 곳의 12개월은 약 29,200행 · 상품 하나의 12개월은 약 1,825행(5매장 × 365일)
  • PK_DW_SLE_DD (SLE_YMD, SHP_NO, ITM_NO): BLEVEL 1 · 리프 약 800블록(리프 한 블록에 약 365항목) · 다른 인덱스 없음 · 통계 최신
  • 이 테이블을 가리키는 FK는 없다

원본 SQL ​

sql
-- R1 본사 대시보드 「어제 매장별 매출」 (하루 수백 번)
SELECT SHP_NO, SUM(SLE_QTY) AS qty, SUM(SLE_AMT) AS amt
  FROM DW_SLE_DD
 WHERE SLE_YMD = :v_ymd
 GROUP BY SHP_NO;

-- R2 점장 앱 「월별 판매 품목 수」 (매장 하나 · 최근 12개월 · 하루 수십 번)
SELECT TRUNC(SLE_YMD / 100) AS ym, COUNT(DISTINCT ITM_NO) AS itm_cnt
  FROM DW_SLE_DD
 WHERE SHP_NO = :v_shp
   AND SLE_YMD BETWEEN :v_from AND :v_to
 GROUP BY TRUNC(SLE_YMD / 100);

-- R3 MD 「상품 12개월 월별 추이」 (상품 하나 · 전 매장 합 · 하루 수백 번)
SELECT TRUNC(SLE_YMD / 100) AS ym, SUM(SLE_QTY) AS qty, SUM(SLE_AMT) AS amt
  FROM DW_SLE_DD
 WHERE ITM_NO = :v_itm
   AND SLE_YMD BETWEEN :v_from AND :v_to
 GROUP BY TRUNC(SLE_YMD / 100);

-- R4 MD 「매장 상품 판매 이력」 (매장 하나 · 상품 하나 · 1년 · 가끔)
SELECT SLE_YMD, SLE_QTY, SLE_AMT
  FROM DW_SLE_DD
 WHERE SHP_NO = :v_shp
   AND ITM_NO = :v_itm
   AND SLE_YMD >= :v_from
 ORDER BY SLE_YMD;

실행계획 학습용 예시 ​

xplan
[R2 · 매장 1003 · 20230101 ~ 20231231]
----------------------------------------------------------------------------------------------
| Id  | Operation         | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |              |      1 |        |     12 |00:00:00.02 |     402 |
|   1 |  SORT GROUP BY    |              |      1 |     12 |     12 |00:00:00.02 |     402 |
|*  2 |   INDEX RANGE SCAN| PK_DW_SLE_DD |      1 |  29200 |  29190 |00:00:00.02 |     402 |
----------------------------------------------------------------------------------------------
   2 - access("SLE_YMD">=:V_FROM AND "SHP_NO"=:V_SHP AND "SLE_YMD"<=:V_TO)
       filter("SHP_NO"=:V_SHP)

[R3 · 상품 1010 · 20230101 ~ 20231231]
---------------------------------------------------------------------------------------------
| Id  | Operation          | Name      | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |           |      1 |        |     12 |00:00:00.02 |    1460 |
|   1 |  HASH GROUP BY     |           |      1 |     12 |     12 |00:00:00.02 |    1460 |
|*  2 |   TABLE ACCESS FULL| DW_SLE_DD |      1 |   1825 |   1825 |00:00:00.02 |    1460 |
---------------------------------------------------------------------------------------------
   2 - filter(("ITM_NO"=:V_ITM AND "SLE_YMD">=:V_FROM AND "SLE_YMD"<=:V_TO))

[R1 · 20231215]  INDEX RANGE SCAN PK_DW_SLE_DD · access("SLE_YMD"=:V_YMD) · 테이블 2블록 · Buffers 5
[R4 · 매장 1003 · 상품 1010 · 2023년]  INDEX RANGE SCAN PK_DW_SLE_DD · access(SLE_YMD 범위) · filter(SHP_NO, ITM_NO) · 테이블 365블록 · Buffers 767

문제 ​

  1. 지금 PK로 R1 ~ R4가 각각 어떻게 처리되는지 설명하고, R2와 R3이 무거운 이유를 PK의 선두 컬럼으로 설명하시오.
  2. PK 컬럼 순서를 바꾸는 안과 인덱스를 더하는 안을 함께 따져 R1 ~ R4를 받치는 인덱스 세트를 설계하시오. 각 리포트가 어느 인덱스를 어떻게 쓰는지 설명하시오.
  3. 날짜만 주는 R1을 새 설계에서 어떻게 받치는지 SKIP SCAN의 조건으로 설명하고, 이 설계의 대가와 반영 절차를 설명하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자