테마
본사 분석용 사본 ORD_RANDOM을 읽는 화면이 넷 있다. 모두 바인드 변수로 쓰여 있다. 그중 점장이 쓰는 「매장 기간 주문 목록」(S1)은 기간이 짧은 날 느리다가, 다른 날은 긴 기간이 끝없이 돈다. 「매장 최근 주문」(S3)은 늘 0.3초쯤 걸린다.
박팀장이 말했다. "S1에 힌트를 박을지, 문장을 나눌지, 인덱스를 만들지 네 화면을 같이 보고 정해 주세요. 사본은 매일 밤 다시 적재하는데, 적재가 06:00 전에 끝나야 합니다."
ORD_RANDOM: 1,000,000행 · 22,414블록 · POS 원장 ORD를 무작위 순서로 복제한 사본 · ORD_DT VARCHAR2(8) 하루 약 1,370건 · SHOP_NO 'SH0001' ~ 'SH0200' 매장마다 고르게(하루 약 7건) · ORD_NO 유일ORD_RANDOM_X01 (ORD_DT, ORD_HMS): CF 999,958 · BLEVEL 2 · 리프 3,774블록 · 다른 인덱스는 없다ORD_RANDOM 몫은 TRUNCATE, 직접 경로 적재, X01 유지, 통계 수집을 합쳐 지금 약 2분이다.-- S1 매장 기간 주문 목록 (점장 · 하루 ~ 3개월)
SELECT ORD_NO, ORD_DT, ORD_HMS
FROM ORD_RANDOM
WHERE SHOP_NO = :shop
AND ORD_DT BETWEEN :f AND :t
ORDER BY ORD_DT, ORD_HMS;
-- S2 시간대별 주문 수 (본사 · 하루)
SELECT SUBSTR(ORD_HMS, 1, 2) AS HH, COUNT(*) AS ORD_CNT
FROM ORD_RANDOM
WHERE ORD_DT = :d
GROUP BY SUBSTR(ORD_HMS, 1, 2);
-- S3 매장 최근 주문 20건 (점장)
SELECT ORD_NO, ORD_DT, ORD_HMS
FROM (SELECT ORD_NO, ORD_DT, ORD_HMS FROM ORD_RANDOM
WHERE SHOP_NO = :shop
ORDER BY ORD_DT DESC, ORD_HMS DESC)
WHERE ROWNUM <= 20;
-- S4 월 정산 리포트 (한 달 · 자기 문장 · FULL 힌트)
SELECT /*+ FULL(o) */ o.SHOP_NO, COUNT(*) AS ORD_CNT
FROM ORD_RANDOM o
WHERE o.ORD_DT BETWEEN :f AND :t
GROUP BY o.SHOP_NO;
[S1 — 커서가 하루로 태어난 날 · 3개월(124,670행 중 그 매장 623행) 한 번]
-------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 623 |00:00:01.12 | 125K |
| 1 | SORT ORDER BY | | 1 | 7 | 623 |00:00:01.12 | 125K |
|* 2 | FILTER | | 1 | | 623 |00:00:01.11 | 125K |
|* 3 | TABLE ACCESS BY INDEX ROWID BATCHED| ORD_RANDOM | 1 | 7 | 623 |00:00:01.11 | 125K |
|* 4 | INDEX RANGE SCAN | ORD_RANDOM_X01 | 1 | 1370 | 124K |00:00:00.03 | 482 |
-------------------------------------------------------------------------------------------------------------
2 - filter(:F<=:T)
3 - filter("SHOP_NO"=:SHOP)
4 - access("ORD_DT">=:F AND "ORD_DT"<=:T)
[S1 — 커서가 3개월로 태어난 날 · 하루(그 매장 7행) 한 번]
-------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 7 |00:00:00.29 | 22414 |
| 1 | SORT ORDER BY | | 1 | 623 | 7 |00:00:00.29 | 22414 |
|* 2 | FILTER | | 1 | | 7 |00:00:00.29 | 22414 |
|* 3 | TABLE ACCESS FULL | ORD_RANDOM | 1 | 623 | 7 |00:00:00.29 | 22414 |
-------------------------------------------------------------------------------------------------------------
3 - filter(("SHOP_NO"=:SHOP AND "ORD_DT">=:F AND "ORD_DT"<=:T))
[S2 — 어느 날이든] INDEX RANGE SCAN ORD_RANDOM_X01 (테이블에 가지 않는다) · 하루 1,370행 · Buffers 7
[S3 — 어느 매장이든] TABLE ACCESS FULL + SORT ORDER BY STOPKEY · Buffers 22,414 · 0.3초
[S4 — 자기 문장] TABLE ACCESS FULL · Buffers 22,414 · 한 달에 맞는 계획
[S1 — 하루로 태어난 날의 하루 한 번] X01 + 테이블 · Buffers 1,377 (그 매장 7행을 위해 1,370행을 테이블까지 가서 거른다)