Skip to content
튜닝 연습옵티마이저와 통계
OPT-021

분기 누적 지역별 매출 화면이 읽는 블록이 1월 초 6천에서 3월 말 8만으로 늘었다

기초SELECT한빛유통611 / 700

상황 ​

영업기획팀의 「분기 누적 지역별 매출」은 분기 첫날부터 지금까지의 주문을 지역별로 묶는다. 팀장 여덟 명이 하루에 수십 번 열고, 화면은 분기 첫날과 다음 분기 첫날을 리터럴로 넣는다. 1월 첫 주에는 6천 블록 남짓 읽던 SQL이 3월 28일 AWR에서 Buffer Gets 상위에 올라왔다. 지금은 한 번에 8만 블록을 읽는다. 2025년부터 주문은 매일 밤 약 913건씩 T_ORD_BIG 끝에 날짜순으로 붙고, 통계는 자동 수집에 맡겨 두었다.

테이블과 인덱스 ​

  • T_ORD_BIG: 지금 1,078,518행 · 6,283블록(2024년까지 1,000,000행 + 2025-01-01 ~ 03-27의 78,518행)
  • 통계: 2024-12-31 22:10 자동 수집 · NUM_ROWS 1,000,000 · ORD_DT NUM_DISTINCT 1,095 · 최솟값 2022-01-01 · 최댓값 2024-12-30 · 히스토그램 없음
  • STALE_PERCENT 10(기본값) · STALE_STATS NO · USER_TAB_MODIFICATIONS INSERTS 78,518
  • 인덱스 IX_ORD_BIG_DT_CUS_AMT (ORD_DT, CUS_ID, ORD_AMT): 이 화면을 위해 작년에 만든 커버링 인덱스 · BLEVEL 2 · 리프 약 4,900블록
  • M_CUS: 2,000행 · 30블록 · PK_M_CUS (CUS_ID) · RGN_ID 8곳 / M_RGN: 8행 · 4블록 · PK_M_RGN (RGN_ID)

원본 SQL ​

sql
SELECT r.RGN_NM, COUNT(*) AS ord_cnt, SUM(o.ORD_AMT) AS amt
  FROM T_ORD_BIG o, M_CUS c, M_RGN r
 WHERE o.ORD_DT >= DATE '2025-01-01'
   AND o.ORD_DT <  DATE '2025-04-01'
   AND c.CUS_ID = o.CUS_ID
   AND r.RGN_ID = c.RGN_ID
 GROUP BY r.RGN_NM

실행계획 학습용 예시 · 최댓값 밖 추정은 교재 10화 측정 ​

xplan
[3월 28일]
------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name                  | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                       |      1 |        |      8 |00:00:00.09 |   80538 |
|   1 |  HASH GROUP BY                 |                       |      1 |      8 |      8 |00:00:00.09 |   80538 |
|*  2 |   HASH JOIN                    |                       |      1 |    913 |  78518 |00:00:00.08 |   80538 |
|   3 |    TABLE ACCESS FULL           | M_RGN                 |      1 |      8 |      8 |00:00:00.01 |       4 |
|   4 |    NESTED LOOPS                |                       |      1 |    913 |  78518 |00:00:00.07 |   80534 |
|   5 |     NESTED LOOPS               |                       |      1 |    913 |  78518 |00:00:00.03 |    2016 |
|*  6 |      INDEX RANGE SCAN          | IX_ORD_BIG_DT_CUS_AMT |      1 |    913 |  78518 |00:00:00.01 |     360 |
|*  7 |      INDEX UNIQUE SCAN         | PK_M_CUS              |  78518 |      1 |  78518 |00:00:00.01 |    1656 |
|   8 |     TABLE ACCESS BY INDEX ROWID| M_CUS                 |  78518 |      1 |  78518 |00:00:00.03 |   78518 |
------------------------------------------------------------------------------------------------------------
   2 - access("R"."RGN_ID"="C"."RGN_ID")
   6 - access("O"."ORD_DT">=TO_DATE(' 2025-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND
              "O"."ORD_DT"<TO_DATE(' 2025-04-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))
   7 - access("C"."CUS_ID"="O"."CUS_ID")

[같은 SQL · 1월 7일]  같은 계획 · E-Rows 913 / A-Rows 6,391 · Buffers 6,560

문제 ​

  1. 6번 줄의 E-Rows 913이 어디서 나온 숫자인지 통계로 설명하고, 날이 갈수록 Buffers가 늘어난 이유를 Starts로 설명하시오.
  2. SQL은 그대로 두고 오늘 할 조치와, 다음 분기에 같은 일이 생기지 않게 할 방법을 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자