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

통계를 매일 모으기 시작한 다음 날부터 지역 주간 주문 화면이 블록을 일흔 배 넘게 읽는다

중급PLSQL한빛유통641 / 700

상황 ​

분기 누적 화면 사고 뒤에 한빛유통은 4월 16일 새벽부터 01:00 적재 배치 끝에서 T_ORD_BIG 통계를 매일 모은다. 그날 아침부터 영업 담당 40명이 쓰는 「지역 주간 주문」 화면(하루 약 3,000번)이 무거워졌다. 오과장은 이렇게 말했다. "매일 수집 때문이네요. 지난주 통계로 되돌리고 매일 수집은 빼죠." 선배는 고개를 저었다. "분기 화면은 그 수집 덕에 살아났어." 조민우가 AWR과 커서 캐시를 뽑았다.

테이블과 인덱스 ​

  • T_ORD_BIG: 2024년 말까지 1,000,000행 + 2025년 1월부터 매일 01:00에 전날 주문 약 913건씩 날짜순으로 끝에 붙는다(4월 16일 현재 약 1,096,000행 · 6,386블록)
  • 2022 ~ 2024년의 100만 행은 날짜와 상관없는 순서로 실려 있어 IX_ORD_BIG_DT (ORD_DT)의 CLUSTERING_FACTOR가 약 1,000,000이다. 2025년 행은 테이블 끝 블록에 날짜순으로 몰려 있다(하루치가 6블록 안팎).
  • M_CUS: 2,000행 · 30블록 · RGN_ID 8곳(지역마다 약 250명) · PK_M_CUS (CUS_ID)
  • 통계: 4월 16일 01:12부터 매일 수집(무효화 기본값). 그 전 통계는 3월 31일 것(ORD_DT 최댓값 2025-03-30)
  • AWR: 1시간 간격 · 보존 8일 · Diagnostic Pack 계약 있음

원본 SQL ​

sql
-- 「지역 주간 주문」 (SQL_ID 4kq7c2v9x1m0d) : 지난 7일 주문을 지역 단위로
SELECT c.CUS_ID, c.CUS_NM, o.ORD_DT, o.ORD_ST, o.ORD_AMT
  FROM M_CUS c, T_ORD_BIG o
 WHERE c.RGN_ID = :rgn
   AND o.CUS_ID = c.CUS_ID
   AND o.ORD_DT >= TRUNC(SYSDATE) - 7
   AND o.ORD_DT <  TRUNC(SYSDATE)
 ORDER BY o.ORD_DT DESC, c.CUS_ID;

-- 오과장 안 : 통계를 지난주로 되돌린다 (매일 수집도 뺀다)
BEGIN
  DBMS_STATS.RESTORE_TABLE_STATS(USER, 'T_ORD_BIG',
    as_of_timestamp => TIMESTAMP '2025-04-15 23:00:00',
    no_invalidate   => FALSE);
END;
/

실행계획 학습용 예시 ​

xplan
[DBA_HIST_SQLSTAT · SQL_ID 4kq7c2v9x1m0d]
 SNAP_ID  시작          PLAN_HASH_VALUE  EXECUTIONS_DELTA  BUFFER_GETS_DELTA  실행당 블록
    9101  04-15 10:00      3528841177               262             23,318           89
    9102  04-15 11:00      3528841177               275             24,475           89
    9122  04-16 07:00      3528841177                31              2,759           89
    9123  04-16 08:00      1201550986               248          1,594,640        6,430
    9124  04-16 09:00      1201550986               281          1,806,830        6,430

[DBA_OPTSTAT_OPERATIONS]
 04-16 01:12:07 ~ 01:12:09  gather_table_stats  SQLP.T_ORD_BIG  (적재 배치 · no_invalidate 기본값)

[V$SQL · 지금]  자식 0 하나 · PLAN_HASH_VALUE 1201550986 · LAST_LOAD_TIME 04-16 07:41 (3528841177 커서는 없다)

[지금 계획 · ALLSTATS LAST · :rgn = 3]
Plan hash value: 1201550986

-------------------------------------------------------------------------------------------------------------
| Id  | Operation           | Name      | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Used-Mem |
-------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |           |      1 |        |    801 |00:00:00.06 |    6430 |          |
|   1 |  SORT ORDER BY      |           |      1 |    799 |    801 |00:00:00.06 |    6430 |   83968 (0)|
|*  2 |   HASH JOIN         |           |      1 |    799 |    801 |00:00:00.06 |    6430 | 1236K (0)|
|*  3 |    TABLE ACCESS FULL| M_CUS     |      1 |    250 |    250 |00:00:00.01 |      30 |          |
|*  4 |    TABLE ACCESS FULL| T_ORD_BIG |      1 |   6391 |   6391 |00:00:00.05 |    6400 |          |
-------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("O"."CUS_ID"="C"."CUS_ID")
   3 - filter("C"."RGN_ID"=:RGN)
   4 - filter(("O"."ORD_DT">=TRUNC(SYSDATE@!)-7 AND "O"."ORD_DT"<TRUNC(SYSDATE@!)))

[4월 15일의 계획 · DBMS_XPLAN.DISPLAY_AWR('4kq7c2v9x1m0d', 3528841177) · AWR에는 실행 통계와 조건절이 남지 않는다]
---------------------------------------------------------------------------------
| Id  | Operation                             | Name          | Rows  | Cost    |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |               |       |     951 |
|   1 |  SORT ORDER BY                        |               |   114 |     951 |
|   2 |   HASH JOIN                           |               |   114 |     950 |
|   3 |    TABLE ACCESS FULL                  | M_CUS         |   250 |      11 |
|   4 |    TABLE ACCESS BY INDEX ROWID BATCHED| T_ORD_BIG     |   913 |     938 |
|   5 |     INDEX RANGE SCAN                  | IX_ORD_BIG_DT |   913 |       4 |
---------------------------------------------------------------------------------

문제 ​

  1. 이 화면의 계획이 언제, 무엇 때문에 바뀌었는지 위 자료로 설명하시오. 통계는 맞아졌는데 왜 더 많이 읽는 계획을 골랐는지도 쓰시오.
  2. 오과장 안의 문제를 쓰고, 통계는 그대로 둔 채 이 화면만 4월 15일의 계획으로 돌아가게 하는 SQL을 쓰시오. 남는 일도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자