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

정산팀 주문 모니터에 새로 붙은 위젯 셋이 5분마다 주문 테이블을 한 번씩 통째로 읽는다

실전DDL한빛유통365 / 700

상황 ​

정산팀 「주문 모니터」 개편으로 위젯 셋이 새로 붙었다. 정산팀 PC 30대가 08 ~ 20시에 5분마다 새로 고쳐서 위젯마다 하루 약 4,300번 돈다. 오픈 첫 주에 세 위젯의 SQL이 AWR 논리 읽기 1 ~ 3위를 차지했다. 위젯 설명서는 이렇다. W1 「상태별 최근 주문 10건」(같은 날이면 주문번호 역순), W2 「최근 7일 일자별 고액 주문 TOP 5」(금액이 같으면 공동 순위로 모두 보여 준다), W3 「이번 달 등급별 매출 상위 고객 10명」(공동 순위 포함). 박팀장은 "주문 테이블 인덱스는 지금 둘에서 하나까지만 더할 수 있다. 위젯마다 무엇을 정렬하고 어디서 멈추는지 적어 달라"라고 했다.

테이블과 인덱스 ​

  • T_ORD_BIG: 1,000,000행 · 5,826블록 · ORD_DT 2022-01-01 ~ 2024-12-31(전부 자정, 하루 913건) · ORD_ST COMPLETE 90% · PENDING 8% · CANCEL 2%(NOT NULL, 값은 셋뿐) · ORD_AMT 1만 ~ 50만 원에 고르게 분포 · ORD_SEQ 유일 · 행이 날짜순으로 놓여 있지 않다(날짜 인덱스 CF 약 1,000,000)
  • 인덱스 IX_ORD_BIG_DT_AMT (ORD_DT, ORD_AMT): 「기간 매출」 화면용 · BLEVEL 2 · 리프 3,206블록(리프 한 블록에 약 312항목)
  • 인덱스 IX_ORD_BIG_CUS_DT (CUS_ID, ORD_DT): CS 화면용 · BLEVEL 2 · 리프 약 3,000블록
  • M_CUS: 2,000행 · 30블록 · CUS_GD A 1% · B 9% · C 90% · 이번 달(2024년 12월) 주문은 28,303건 · 통계 최신

원본 SQL ​

sql
-- W1 상태별 최근 주문 10건
SELECT ORD_ST, ORD_SEQ, CUS_ID, ORD_DT, ORD_AMT
  FROM (SELECT ORD_ST, ORD_SEQ, CUS_ID, ORD_DT, ORD_AMT,
               ROW_NUMBER() OVER (PARTITION BY ORD_ST ORDER BY ORD_DT DESC, ORD_SEQ DESC) AS rn
          FROM T_ORD_BIG)
 WHERE rn <= 10;

-- W2 최근 7일 일자별 고액 주문 TOP 5
SELECT ORD_DT, ORD_SEQ, CUS_ID, ORD_AMT, rn
  FROM (SELECT ORD_DT, ORD_SEQ, CUS_ID, ORD_AMT,
               ROW_NUMBER() OVER (PARTITION BY ORD_DT ORDER BY ORD_AMT DESC, ORD_SEQ DESC) AS rn
          FROM T_ORD_BIG
         WHERE ORD_DT >= :d - 6 AND ORD_DT < :d + 1)
 WHERE rn <= 5;

-- W3 이번 달 등급별 매출 상위 고객 10명
SELECT CUS_GD, CUS_ID, AMT, rk
  FROM (SELECT c.CUS_GD, o.CUS_ID, SUM(o.ORD_AMT) AS AMT,
               RANK() OVER (PARTITION BY c.CUS_GD ORDER BY SUM(o.ORD_AMT) DESC) AS rk
          FROM T_ORD_BIG o, M_CUS c
         WHERE o.ORD_DT >= :m1 AND o.ORD_DT < ADD_MONTHS(:m1, 1)
           AND c.CUS_ID = o.CUS_ID
         GROUP BY c.CUS_GD, o.CUS_ID)
 WHERE rk <= 10;

실행계획 학습용 예시 ​

xplan
[W1]
-------------------------------------------------------------------------------------------------------------
| Id  | Operation                | Name      | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
-------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |           |      1 |        |     30 |00:00:00.43 |    5826 |          |
|*  1 |  VIEW                    |           |      1 |   1000K|     30 |00:00:00.43 |    5826 |          |
|*  2 |   WINDOW SORT PUSHED RANK|           |      1 |   1000K|     30 |00:00:00.43 |    5826 | 4096  (0)|
|   3 |    TABLE ACCESS FULL     | T_ORD_BIG |      1 |   1000K|   1000K|00:00:00.06 |    5826 |          |
-------------------------------------------------------------------------------------------------------------
   1 - filter("RN"<=10)
   2 - filter(ROW_NUMBER() OVER ( PARTITION BY "ORD_ST" ORDER BY INTERNAL_FUNCTION("ORD_DT") DESC ,
              INTERNAL_FUNCTION("ORD_SEQ") DESC )<=10)

[W2 · :d = 2024-12-31]
-------------------------------------------------------------------------------------------------------------
| Id  | Operation                | Name      | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
-------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |           |      1 |        |     35 |00:00:00.08 |    5826 |          |
|*  1 |  VIEW                    |           |      1 |   6391 |     35 |00:00:00.08 |    5826 |          |
|*  2 |   WINDOW SORT PUSHED RANK|           |      1 |   6391 |     35 |00:00:00.08 |    5826 | 2048  (0)|
|*  3 |    TABLE ACCESS FULL     | T_ORD_BIG |      1 |   6391 |   6391 |00:00:00.06 |    5826 |          |
-------------------------------------------------------------------------------------------------------------
   1 - filter("RN"<=5)
   2 - filter(ROW_NUMBER() OVER ( PARTITION BY "ORD_DT" ORDER BY INTERNAL_FUNCTION("ORD_AMT") DESC ,
              INTERNAL_FUNCTION("ORD_SEQ") DESC )<=5)
   3 - filter(("ORD_DT">=:D-6 AND "ORD_DT"<:D+1))

[W3 · :m1 = 2024-12-01]
-------------------------------------------------------------------------------------------------------------
| Id  | Operation                | Name      | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
-------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |           |      1 |        |     30 |00:00:00.19 |    5856 |          |
|*  1 |  VIEW                    |           |      1 |   2000 |     30 |00:00:00.19 |    5856 |          |
|*  2 |   WINDOW SORT PUSHED RANK|           |      1 |   2000 |     30 |00:00:00.19 |    5856 | 4096  (0)|
|   3 |    HASH GROUP BY         |           |      1 |   2000 |   2000 |00:00:00.19 |    5856 | 1412K (0)|
|*  4 |     HASH JOIN            |           |      1 |  28303 |  28303 |00:00:00.17 |    5856 | 1236K (0)|
|   5 |      TABLE ACCESS FULL   | M_CUS     |      1 |   2000 |   2000 |00:00:00.01 |      30 |          |
|*  6 |      TABLE ACCESS FULL   | T_ORD_BIG |      1 |  28303 |  28303 |00:00:00.15 |    5826 |          |
-------------------------------------------------------------------------------------------------------------
   1 - filter("RK"<=10)
   2 - filter(RANK() OVER ( PARTITION BY "C"."CUS_GD" ORDER BY SUM("O"."ORD_AMT") DESC )<=10)
   4 - access("C"."CUS_ID"="O"."CUS_ID")
   6 - filter(("O"."ORD_DT">=:M1 AND "O"."ORD_DT"<ADD_MONTHS(:M1,1)))

문제 ​

  1. 세 위젯이 각각 몇 행을 읽어 몇 줄을 남기는지 실행계획으로 쓰고, W1이 WINDOW SORT PUSHED RANK인데도 주문 테이블을 다 읽는 까닭을 설명하시오.
  2. 인덱스를 하나만 더할 수 있다는 조건에서 세 위젯의 SQL과 인덱스를 설계하시오. W2는 위젯 설명서의 동점 규칙을 지키시오.
  3. 설계안의 대가(주문 적재와 기존 화면에 주는 영향)와 위젯마다 남는 일량을 숫자로 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자