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

칸 단위로만 통계를 모은 뒤 버거 데이 매출 비교가 주문을 통째로 읽는다

실전PLSQL버거604 / 700

상황 ​

버거 본사는 7월에 POS 원장을 월별 파티션 테이블 ORD_RANGE로 옮겼다. 빈 테이블로 만들고 칸마다 원장을 날짜순으로 옮겨 실은 뒤, 업체 가이드("파티션 테이블은 바뀐 칸만 모은다")대로 칸 단위로만 통계를 모았다. 지금도 야간 통계 잡은 적재가 있었던 이번 달 칸만 granularity => 'PARTITION'으로 모은다. 마케팅팀의 「버거 데이 매출 비교」(매달 둘째 화요일, 5월부터 8번)가 22,000블록 넘게 읽는다. 오과장은 이렇게 말한다. "테이블 전체를 다시 모으면 밤마다 24칸을 다 읽어요. 칸이 늘수록 더 길어지고요." 박팀장: "어느 방식으로 갈지 근거를 붙여서."

테이블과 인덱스 ​

  • ORD_RANGE: ORD와 같은 열 + ORD_YM VARCHAR2(6)(파티션 키) · 1,000,000행 · 월별 24칸(P201201 ~ P201312) · 칸마다 약 41,700행, 약 935블록, 합계 약 22,440블록 · 날짜순으로 실었다
  • 로컬 인덱스 ORD_RANGE_X01 (ORD_DT, ORD_HMS) LOCAL · 칸마다 높이 2
  • 하루 주문 약 1,370건 · 매장 200곳
  • 칸 통계는 칸마다 정확하다(ORD_DT NDV 28 ~ 31). 전역 통계는 한 번도 따로 모으지 않았다: GLOBAL_STATS NO(칸 통계를 합친 값) · NUM_ROWS 1,000,000 · ORD_DT NUM_DISTINCT 31(실제 730)
  • 선호도는 기본값(INCREMENTAL FALSE) · 통계 이력 31일

원본 SQL ​

sql
-- 야간 통계 잡 (업체 가이드 · 적재가 있었던 이번 달 칸만)
BEGIN
  DBMS_STATS.GATHER_TABLE_STATS(ownname     => 'DEV',
                                tabname     => 'ORD_RANGE',
                                partname    => 'P' || TO_CHAR(SYSDATE, 'YYYYMM'),
                                granularity => 'PARTITION');
END;
/

-- 마케팅 「버거 데이 매출 비교」 (매달 둘째 화요일)
SELECT ORD_DT, SHOP_NO, COUNT(*) AS ord_cnt
  FROM ORD_RANGE
 WHERE ORD_DT IN ('20130514', '20130611', '20130709', '20130813',
                  '20130910', '20131008', '20131112', '20131210')
 GROUP BY ORD_DT, SHOP_NO

실행계획 학습용 예시 ​

xplan
[버거 데이 매출 비교]
---------------------------------------------------------------------------------------------------------------
| Id  | Operation            | Name      | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Pstart| Pstop |
---------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |           |      1 |        |   1600 |00:00:00.31 |   22440 |       |       |
|   1 |  HASH GROUP BY       |           |      1 |   6200 |   1600 |00:00:00.31 |   22440 |       |       |
|   2 |   PARTITION RANGE ALL|           |      1 |    258K|  10960 |00:00:00.30 |   22440 |     1 |    24 |
|*  3 |    TABLE ACCESS FULL | ORD_RANGE |     24 |    258K|  10960 |00:00:00.29 |   22440 |     1 |    24 |
---------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   3 - filter(("ORD_DT"='20130514' OR "ORD_DT"='20130611' OR "ORD_DT"='20130709' OR
              "ORD_DT"='20130813' OR "ORD_DT"='20130910' OR "ORD_DT"='20131008' OR
              "ORD_DT"='20131112' OR "ORD_DT"='20131210'))

[USER_TAB_STATISTICS · USER_TAB_COL_STATISTICS — ORD_RANGE]
 수준        GLOBAL_STATS   NUM_ROWS    ORD_DT NUM_DISTINCT
 전역        NO             1,000,000   31          (칸 통계를 합친 값)
 P201306     YES               41,096   30
 P201312     YES               42,467   31

[야간 통계 잡 — 방식별 (복사본 · 학습용 예시)]
 방식                                          읽는 블록    걸린 시간   전역 ORD_DT NDV
 이번 달 칸만 (granularity PARTITION · 지금)      약 935       0.4초       31
 테이블 전체 (granularity AUTO)                 약 22,440      6.1초      730
 INCREMENTAL TRUE · 첫 수집                     약 22,440      7.3초      730   (칸마다 시놉시스를 남긴다)
 INCREMENTAL TRUE · 다음 날부터 (바뀐 칸 하나)     약 935       0.6초      730   (시놉시스를 합친다)

문제 ​

  1. 2번 줄의 E-Rows 258K를 손으로 계산하고, 칸 통계가 모두 정확한데도 전역 추정이 틀린 이유를 쓰시오. 조건에 ORD_YM을 더하면 고쳐지는지도 쓰시오.
  2. 세 방식(이번 달 칸만, 테이블 전체, INCREMENTAL)을 읽는 양과 전역 통계의 정확도, 부작용으로 비교하시오.
  3. 고른 방식으로 통계 설정과 야간 잡을 고쳐 쓰시오. 앞으로 새 칸이 생길 때 할 일도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자