테마
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-31IX_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)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-- 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;
[리허설 — 시험 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)