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

주문 테이블을 고객 순서로 다시 실은 주말 뒤로 정산팀 월 주문 합계가 블록을 여든 배 넘게 읽는다

중급PLSQL한빛유통596 / 700

상황 ​

상담팀이 「고객 주문 내역」 화면이 굼뜨다고 하자, 오과장은 토요일 밤 T_ORD를 고객 순서로 다시 실었다. 04화에서 본 대로 테이블을 인덱스 순서로 다시 쌓으면 그 인덱스의 클러스터링 팩터가 좋아진다. 월요일에 상담 화면은 실제로 빨라졌다(고객 한 명 Buffers 52 → 4). 그런데 정산팀 「월 주문 합계」(월을 고르면 그 달의 건수와 금액, 하루 수백 번)가 블록을 여든 배 넘게 읽는다. 오과장은 이렇게 말했다. "계획은 지난주와 똑같아요. 인덱스도 그대로 타고, 통계도 토요일 밤에 야간 잡이 다 모았어요." 조민우가 인덱스 통계를 다시 모아 보자고 하자 오과장이 덧붙였다. "그러면 날짜 인덱스 CF가 10만 가까이 나와서 FULL로 갈 거예요. TABLE_CACHED_BLOCKS를 최대로 올리면 CF가 작게 나온다니까 그걸 같이 하죠."

테이블과 인덱스 ​

  • T_ORD: 100,000행 · 896블록(블록당 약 112행) · ORD_DT DATE(시분초 있음, 2023-01-01 ~ 2024-12-31, 하루 약 137건) · 2024년 6월 4,110건 · 고객 2,000명 × 50건
  • 토요일 전에는 주문 순서(곧 날짜순)로 쌓여 있었다. 지금은 CUS_ID, ORD_DT 순서로 쌓여 있어서 한 고객의 주문 50건이 블록 한두 개에 모여 있다.
  • 인덱스: PK_T_ORD (ORD_SEQ) · IX_T_ORD_DT (ORD_DT) BLEVEL 1 · 리프 265 · IX_T_ORD_CUS (CUS_ID) BLEVEL 1 · 리프 298 · 세 인덱스 모두 2024년 3월에 만들었다
  • 야간 통계 잡 PKG_STATS.NIGHTLY(매일 23:40): 2021년에 T_ORD_BIG 인덱스 셋을 모으는 시간을 줄이려고 cascade => FALSE로 바꿨다. 주석에는 "인덱스 통계는 만들 때와 리빌드할 때 모인다"라고 적혀 있다.
  • T_ORD의 TABLE_CACHED_BLOCKS 선호도는 기본값(1)이다.

원본 SQL ​

sql
-- 토요일 22:00 · 오과장 : 상담 화면을 위해 T_ORD 를 고객 순서로 다시 싣는다 (인덱스 셋은 그대로 둔다)
CREATE TABLE T_ORD_SORT AS SELECT * FROM T_ORD ORDER BY CUS_ID, ORD_DT;
TRUNCATE TABLE T_ORD;
INSERT INTO T_ORD SELECT * FROM T_ORD_SORT;
COMMIT;
DROP TABLE T_ORD_SORT PURGE;
-- 통계는 야간 잡에 맡긴다

-- 야간 통계 잡 PKG_STATS.NIGHTLY (매일 23:40 · 2021-04 판)
BEGIN
  DBMS_STATS.GATHER_SCHEMA_STATS(ownname => 'SQLP',
                                 options => 'GATHER STALE',
                                 cascade => FALSE);   -- 인덱스 통계는 만들 때와 리빌드할 때 모인다
END;
/

-- 정산팀 「월 주문 합계」
SELECT COUNT(*) AS cnt, SUM(ORD_AMT) AS amt, SUM(PAY_AMT) AS pay
  FROM T_ORD
 WHERE ORD_DT >= :v_from
   AND ORD_DT <  :v_to

실행계획 학습용 예시 · 클러스터링 팩터의 성질은 교재 04화 측정 ​

xplan
[USER_TAB_STATISTICS · USER_INDEXES · 월요일 09:30]
 OBJECT_NAME    종류     NUM_ROWS  BLOCKS / LEAF_BLOCKS  CLUSTERING_FACTOR  LAST_ANALYZED
 T_ORD          TABLE     100,000                   896                  -  토 23:41 (야간 잡)
 PK_T_ORD       INDEX     100,000                   187                889  2024-03-11 (인덱스를 만든 날)
 IX_T_ORD_DT    INDEX     100,000                   265                892  2024-03-11
 IX_T_ORD_CUS   INDEX     100,000                   298             97,915  2024-03-11

[월 주문 합계 · 2024년 6월 · 월요일 10:05]
Plan hash value: 1830562947

-------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name        | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |             |      1 |        |      1 |00:00:00.03 |    4118 |
|   1 |  SORT AGGREGATE               |             |      1 |      1 |      1 |00:00:00.03 |    4118 |
|*  2 |   FILTER                      |             |      1 |        |   4110 |00:00:00.03 |    4118 |
|   3 |    TABLE ACCESS BY INDEX ROWID| T_ORD       |      1 |   4105 |   4110 |00:00:00.03 |    4118 |
|*  4 |     INDEX RANGE SCAN          | IX_T_ORD_DT |      1 |   4105 |   4110 |00:00:00.01 |      12 |
-------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter(:V_FROM<:V_TO)
   4 - access("ORD_DT">=:V_FROM AND "ORD_DT"<:V_TO)

[같은 SQL · 지난주 금요일]  Plan hash value 1830562947 (같은 계획) · Buffers 49 · 0.01초

문제 ​

  1. 계획이 지난주와 같은데 읽는 블록이 여든 배 넘게 늘어난 이유를 쓰시오. USER_INDEXES의 어느 숫자가 지금 테이블과 맞지 않는지, 그 숫자가 왜 고쳐지지 않았는지도 쓰시오.
  2. 통계를 바로잡는 SQL과 고친 야간 잡을 쓰시오. TABLE_CACHED_BLOCKS를 올리자는 오과장의 안을 평가하고, 그 선호도가 맞는 테이블은 어떤 테이블인지 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자