테마
한빛유통 「주문 조회」 화면을 개편한다. 주문일자 범위는 필수이고(최대 31일), 고객번호, 주문상태, 결제수단은 선택이다. 결과는 최근 주문부터 50건씩 보여 준다. 개발팀은 고른 조건 조합마다 SQL을 따로 만들어 보내도록 짰고(동적 SQL), 인덱스 요청서에는 "조건으로 쓰는 컬럼마다 하나씩: CUS_ID, ORD_ST, PAY_TP"라고 적었다. 박팀장은 운영 화면의 한 달 치 조회 로그를 뽑아 건네며 이렇게 말했다. "조합마다 어떻게 찾아갈지부터 정하고, 인덱스는 최소로."
T_ORD 주문: 100,000행 · 896블록 · ORD_DT DATE(시분초 있음, 2023-01-01 ~ 2024-12-31, 하루 약 137건, 같은 날짜의 주문은 테이블 곳곳에 흩어져 있다) · CUS_ID 2,000명(고객당 50건, 한 달에 약 2건) · ORD_ST COMPLETE 90% · PENDING 8% · CANCEL 2% · PAY_TP CARD · CASH · POINT가 1/3씩PK_T_ORD (ORD_SEQ) · IX_T_ORD_DT (ORD_DT): BLEVEL 1 · 리프 265블록 · CF 약 99,986 · 통계 최신| 조합 | 조건 | 비율 | 보통 고르는 기간 |
|---|---|---|---|
| C1 | 기간 + 고객 | 48% | 한 달 |
| C2 | 기간 + 상태 | 22% | 하루 |
| C3 | 기간만 | 17% | 하루 ~ 일주일 |
| C4 | 기간 + 결제수단 | 9% | 일주일 |
| C5 | 기간 + 상태 + 결제수단 | 4% | 하루 |
-- C1 (다른 조합도 모양이 같고 조건 한 줄만 다르다)
SELECT ORD_SEQ, CUS_ID, ORD_DT, ORD_ST, PAY_TP, ORD_AMT
FROM T_ORD
WHERE ORD_DT >= TO_DATE(:v_from, 'YYYYMMDD')
AND ORD_DT < TO_DATE(:v_to, 'YYYYMMDD') + 1
AND CUS_ID = :v_cus
ORDER BY ORD_DT DESC
FETCH FIRST 50 ROWS ONLY;
-- C2 : … AND ORD_ST = :v_st C3 : 조건 줄 없음
-- C4 : … AND PAY_TP = :v_tp C5 : … AND ORD_ST = :v_st AND PAY_TP = :v_tp
-- 개발팀 요청서
CREATE INDEX IX_T_ORD_CUS ON T_ORD (CUS_ID);
CREATE INDEX IX_T_ORD_ST ON T_ORD (ORD_ST);
CREATE INDEX IX_T_ORD_TP ON T_ORD (PAY_TP);
[C1 · 지금 · CUS_000777 · 2024년 6월]
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 2 |00:00:00.01 | 896 |
|* 1 | VIEW | | 1 | 2 | 2 |00:00:00.01 | 896 |
|* 2 | WINDOW SORT PUSHED RANK| | 1 | 2 | 2 |00:00:00.01 | 896 |
|* 3 | TABLE ACCESS FULL | T_ORD | 1 | 2 | 2 |00:00:00.01 | 896 |
-----------------------------------------------------------------------------------------------
1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=50)
2 - filter(ROW_NUMBER() OVER ( ORDER BY INTERNAL_FUNCTION("ORD_DT") DESC )<=50)
3 - filter(("CUS_ID"=:V_CUS AND "ORD_DT">=TO_DATE(:V_FROM,'YYYYMMDD') AND "ORD_DT"<TO_DATE(:V_TO,'YYYYMMDD')+1))
[C2 · 지금 · PENDING · 2024-06-14]
-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 11 |00:00:00.01 | 139 |
|* 1 | VIEW | | 1 | 11 | 11 |00:00:00.01 | 139 |
|* 2 | WINDOW NOSORT STOPKEY | | 1 | 11 | 11 |00:00:00.01 | 139 |
|* 3 | TABLE ACCESS BY INDEX ROWID | T_ORD | 1 | 11 | 11 |00:00:00.01 | 139 |
|* 4 | INDEX RANGE SCAN DESCENDING| IX_T_ORD_DT | 1 | 137 | 137 |00:00:00.01 | 2 |
-------------------------------------------------------------------------------------------------------
1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=50)
2 - filter(ROW_NUMBER() OVER ( ORDER BY INTERNAL_FUNCTION("ORD_DT") DESC )<=50)
3 - filter("ORD_ST"=:V_ST)
4 - access("ORD_DT"<TO_DATE(:V_TO,'YYYYMMDD')+1 AND "ORD_DT">=TO_DATE(:V_FROM,'YYYYMMDD'))