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

힌트 대장 정리 배포를 앞둔 리허설에서 세 SQL 가운데 하나만 느려졌다

실전SELECT한빛유통632 / 700

상황 ​

06화에서 만든 힌트 유효기간 대장에 재검토 날짜가 지난 줄이 셋 있다. 박팀장의 방침은 18화 그대로다. "계획을 고르는 일은 옵티마이저에, 지키는 일은 베이스라인에." 그래서 작년에 세 SQL은 힌트가 박힌 글자 그대로 커서 캐시에서 베이스라인으로 올려 두었다(ACCEPTED YES). 조민우가 시험 DB(운영 복제, 베이스라인까지 옮김)에서 힌트를 뗀 판으로 리허설을 했다. Q1과 Q2는 괜찮았는데 Q3만 느려졌다. 조민우는 고개를 갸웃했다. "베이스라인이 있는데 왜 계획이 바뀌죠?"

테이블과 인덱스 ​

  • T_ORD_BIG: 1,000,000행 · 5,826블록 · 고객당 500건 · ORD_DT 2022-01-01 ~ 2024-12-31
  • 인덱스: IX_ORD_BIG_DT (ORD_DT)(클러스터링 팩터 1,000,000) · IX_ORD_BIG_DT_AMT (ORD_DT, ORD_AMT)(02화) · IX_ORD_BIG_CUS_DT (CUS_ID, ORD_DT)
  • M_CUS: 2,000행 · 30블록 · CUS_GD A 1%(20명) · B 9% · C 90% · CUS_GD에 히스토그램 없음(통계 정책 SIZE 1)
  • 힌트 대장: Q1 INDEX(o IX_ORD_BIG_CUS_DT) 2019 · Q2 INDEX(o IX_ORD_BIG_DT) 2019(그때는 하루 범위 화면) · Q3 LEADING(c o) USE_NL(o) INDEX(o IX_ORD_BIG_CUS_DT) 2021

원본 SQL ​

sql
-- Q1 「고객 주문 이력」 (하루 8,000번)
SELECT /*+ INDEX(o IX_ORD_BIG_CUS_DT) */ o.ORD_SEQ, o.ORD_DT, o.ORD_AMT
  FROM T_ORD_BIG o
 WHERE o.CUS_ID = :cus
 ORDER BY o.ORD_DT DESC;

-- Q2 「기간 매출 합계」 (하루 300번 · 지금은 한 달 범위)
SELECT /*+ INDEX(o IX_ORD_BIG_DT) */ COUNT(*), SUM(o.ORD_AMT)
  FROM T_ORD_BIG o
 WHERE o.ORD_DT >= :v_from
   AND o.ORD_DT <  :v_to;

-- Q3 「VIP 최근 30일 주문」 (하루 2,000번)
SELECT /*+ LEADING(c o) USE_NL(o) INDEX(o IX_ORD_BIG_CUS_DT) */
       c.CUS_ID, c.CUS_NM, o.ORD_SEQ, o.ORD_DT, o.ORD_AMT
  FROM M_CUS c, T_ORD_BIG o
 WHERE c.CUS_GD = 'A'
   AND o.CUS_ID = c.CUS_ID
   AND o.ORD_DT >= :v_from;

실행계획 학습용 예시 ​

xplan
[리허설 — 시험 DB (운영 복제 · 베이스라인 포함)]
          힌트 있는 판 (지금 운영)                                힌트 뗀 판
 Q1   INDEX RANGE SCAN DESCENDING · PHV 2845019935 · 503      같은 PHV 2845019935 · 503
 Q2   IX_ORD_BIG_DT + 테이블 · 27,466                          IX_ORD_BIG_DT_AMT · 91
 Q3   NL (IX_ORD_BIG_CUS_DT) · 364 · Note: baseline used      HASH JOIN + T_ORD_BIG FULL · 5,856 · Note 없음

[V$SQL — Q3 두 판]
 판          SQL_ID          EXACT_MATCHING_SIGNATURE   PLAN_HASH_VALUE  SQL_PLAN_BASELINE
 힌트 있음   1q7xk2m9d4f0b   11820941736520384410       1730592648       SQL_PLAN_a4n2c8w0x1v9k5e2d7c31
 힌트 없음   8d3m0v6s2k9wq    4467302185903377719       3907114725       (없음)

[Q3 — 힌트 뗀 판 · 리허설]
-----------------------------------------------------------------------------------------------
| Id  | Operation                    | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                   |      1 |        |    274 |00:00:00.11 |    5856 |
|*  1 |  HASH JOIN                   |                   |      1 |   9145 |    274 |00:00:00.11 |    5856 |
|*  2 |   TABLE ACCESS FULL          | M_CUS             |      1 |    667 |     20 |00:00:00.01 |      30 |
|*  3 |   TABLE ACCESS FULL          | T_ORD_BIG         |      1 |  27420 |  27397 |00:00:00.09 |    5826 |
-----------------------------------------------------------------------------------------------

[Q3 — 힌트 있는 판 · 지금 운영]
-----------------------------------------------------------------------------------------------
| Id  | Operation                    | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                   |      1 |        |    274 |00:00:00.01 |     364 |
|   1 |  NESTED LOOPS                |                   |      1 |   9145 |    274 |00:00:00.01 |     364 |
|   2 |   NESTED LOOPS               |                   |      1 |   9145 |    274 |00:00:00.01 |      90 |
|*  3 |    TABLE ACCESS FULL         | M_CUS             |      1 |    667 |     20 |00:00:00.01 |      30 |
|*  4 |    INDEX RANGE SCAN          | IX_ORD_BIG_CUS_DT |     20 |     14 |    274 |00:00:00.01 |      60 |
|   5 |   TABLE ACCESS BY INDEX ROWID| T_ORD_BIG         |    274 |     14 |    274 |00:00:00.01 |     274 |
-----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   (뗀 판)   1 - access("O"."CUS_ID"="C"."CUS_ID")  2 - filter("C"."CUS_GD"='A')  3 - filter("O"."ORD_DT">=:V_FROM)
   (있는 판) 3 - filter("C"."CUS_GD"='A')  4 - access("O"."CUS_ID"="C"."CUS_ID" AND "O"."ORD_DT">=:V_FROM)

문제 ​

  1. 리허설 결과를 근거로 세 힌트를 각각 어떻게 처리할지 정하시오.
  2. Q3에 베이스라인이 있는데도 계획이 바뀐 이유를 베이스라인이 문장을 알아보는 방식으로 설명하고, 힌트를 떼고도 지금 계획을 지키도록 배포 전에 할 일을 SQL로 쓰시오.
  3. 이 방식의 부작용과 유지 방법, Q3에 힌트가 필요했던 진짜 원인을 고치는 길을 서술하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자