테마
영업기획팀의 「매출 분석」 개편으로 T_ORD_BIG을 읽는 SQL 네 개(S1 ~ S4)가 새로 붙는다. 화면 S1은 하루 약 300번, 정산팀 화면 S3은 하루 약 600번, 야간 적재 S2와 월 리포트 S4는 한 번씩 돈다. 개발 DB에서 잰 실행계획을 보면 S1, S2, S4가 테이블을 통째로 읽고 S2는 TEMP까지 쓴다.
박팀장의 주문은 이렇다. "인덱스는 지금 있는 두 개에서 늘리지 않는 것이 목표다. SQL마다 정렬이나 묶기가 사라지는지 남는지 적고, 남기는 것은 왜 남겨도 되는지 써 달라."
T_ORD_BIG: 1,000,000행 · 5,826블록 · ORD_SEQ, CUS_ID, ORD_ST, ORD_AMT는 NOT NULL이고 ORD_DT는 NULL 허용(실제 0건) · ORD_DT는 전부 자정이고 하루 약 913건 · 2024년 4분기 83,941건 · 고객 2,000명, 고객당 약 500건 · 같은 날짜의 주문도 한 고객의 주문도 테이블 곳곳에 흩어져 있다IX_ORD_BIG_DT_SEQ (ORD_DT, ORD_SEQ)(09화, 주문 장부의 키 기반 페이징이 쓴다) · BLEVEL 2 · 리프 3,344블록 · CF 1,000,000 / IX_ORD_BIG_CUS_DT (CUS_ID, ORD_DT)(CS 화면의 고객 한 명 기간 조회가 쓴다) · BLEVEL 2 · 리프 약 3,000블록T_CUS_DAY(고객 구매 달력, 이 문제용 테이블): 매일 밤 TRUNCATE 뒤 S2로 다시 채운다. 고객과 날짜의 조합은 약 80만 개다.-- S1 「분기 일자별 상태별 매출」 (화면, 하루 약 300번)
SELECT ORD_DT, ORD_ST, COUNT(*) AS CNT, SUM(ORD_AMT) AS AMT
FROM T_ORD_BIG
WHERE ORD_DT >= :q_from AND ORD_DT < :q_to
GROUP BY ORD_DT, ORD_ST
ORDER BY ORD_DT, ORD_ST;
-- S2 「고객 구매 달력」 적재 (야간 한 번)
INSERT /*+ APPEND */ INTO T_CUS_DAY (CUS_ID, ORD_DT, CNT, AMT)
SELECT CUS_ID, ORD_DT, COUNT(*), SUM(ORD_AMT)
FROM T_ORD_BIG
GROUP BY CUS_ID, ORD_DT;
-- S3 「일 주문 장부」 (정산팀 화면, 하루 약 600번)
SELECT ORD_SEQ, CUS_ID, ORD_AMT
FROM T_ORD_BIG
WHERE ORD_DT >= :d AND ORD_DT < :d + 1
ORDER BY ORD_SEQ;
-- S4 「고객별 연간 매출 순위」 (월 리포트)
SELECT CUS_ID, SUM(ORD_AMT) AS AMT
FROM T_ORD_BIG
WHERE ORD_DT >= DATE '2024-01-01' AND ORD_DT < DATE '2025-01-01'
GROUP BY CUS_ID
ORDER BY AMT DESC;
[S1 · 2024년 4분기]
------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem | Used-Tmp|
------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 276 |00:00:00.21 | 5826 | | | | |
| 1 | SORT GROUP BY | | 1 | 276 | 276 |00:00:00.21 | 5826 | 31744 | 31744 |28672 (0)| |
|* 2 | TABLE ACCESS FULL | T_ORD_BIG | 1 | 83941 | 83941 |00:00:00.12 | 5826 | | | | |
------------------------------------------------------------------------------------------------------------------------------------------------
2 - filter(("ORD_DT">=:Q_FROM AND "ORD_DT"<:Q_TO))
[S2]
------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem | Used-Tmp|
------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | INSERT STATEMENT | | 1 | | 0 |00:00:06.31 | 5941 | | | | |
| 1 | LOAD AS SELECT | T_CUS_DAY | 1 | | 0 |00:00:06.31 | 5941 | | | | |
| 2 | OPTIMIZER STATISTICS GATHERING | | 1 | 1000K| 802K|00:00:05.12 | 5833 | | | | |
| 3 | HASH GROUP BY | | 1 | 1000K| 802K|00:00:04.97 | 5833 | 46M| 6118K| 20M (1)| 39M|
| 4 | TABLE ACCESS FULL | T_ORD_BIG | 1 | 1000K| 1000K|00:00:00.19 | 5826 | | | | |
------------------------------------------------------------------------------------------------------------------------------------------------
[S3 · 하루]
------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem | Used-Tmp|
------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 913 |00:00:00.01 | 918 | | | | |
| 1 | SORT ORDER BY | | 1 | 913 | 913 |00:00:00.01 | 918 | 59392 | 59392 |53248 (0)| |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| T_ORD_BIG | 1 | 913 | 913 |00:00:00.01 | 918 | | | | |
|* 3 | INDEX RANGE SCAN | IX_ORD_BIG_DT_SEQ | 1 | 913 | 913 |00:00:00.01 | 5 | | | | |
------------------------------------------------------------------------------------------------------------------------------------------------
3 - access("ORD_DT">=:D AND "ORD_DT"<:D+1)
[S4]
------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem | Used-Tmp|
------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 2000 |00:00:00.15 | 5826 | | | | |
| 1 | SORT ORDER BY | | 1 | 2000 | 2000 |00:00:00.15 | 5826 | 83968 | 83968 |74752 (0)| |
| 2 | HASH GROUP BY | | 1 | 2000 | 2000 |00:00:00.15 | 5826 | 1201K| 1201K| 1421K (0)| |
|* 3 | TABLE ACCESS FULL | T_ORD_BIG | 1 | 334K| 333K|00:00:00.09 | 5826 | | | | |
------------------------------------------------------------------------------------------------------------------------------------------------
3 - filter(("ORD_DT">=TO_DATE(' 2024-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND
"ORD_DT"<TO_DATE(' 2025-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')))