테마
본사는 매장의 계약 형태(직영, 가맹), 점주, 수수료율이 바뀔 때마다 매장 계약 이력에 새 구간을 연다. 이 이력을 붙이는 SQL 네 개가 새로 생겼고, 개발팀은 "이력 조인이 다 느릴 테니 이력 테이블에 인덱스를 두세 개 달아 달라"는 요청서를 올렸다. 박팀장은 "주문 테이블에는 POS가 실시간으로 넣으니 인덱스는 하나까지만 허락한다. 네 SQL을 다 보고 꼭 필요한 것만 정해 달라"라고 했다. 지금 느리다고 민원이 들어온 것은 경영진 보고 기간에 하루 50번쯤 도는 Q3뿐이다.
ORD 주문: 1,000,000행 · 22,422블록 · ORD_PK (ORD_NO) · ORD_X01 (ORD_DT, ORD_HMS) · 날짜순 적재(하루 약 1,370건이 약 31블록에 모여 있다) · SHOP_NO 200곳, 매장마다 하루 약 6.8건SHOP_CTRT_HIS 매장 계약 이력 (이 문제에서 쓰는 파생 테이블, 선분이력): SHOP_NO · STA_DT · END_DT (VARCHAR2(8), 그날까지 포함, 지금 구간은 '99991231') · CTRT_TP · OWNER_NM · FEE_RT · CHG_RSN (신규, 가맹전환, 점주변경) · 612행 · 6블록 · SHOP_CTRT_HIS_PK (SHOP_NO, STA_DT) · 매장마다 평균 3구간-- [Q1] 점주 앱 첫 화면: 내 매장의 지금 계약 (하루 2만 번)
SELECT CTRT_TP, OWNER_NM, FEE_RT
FROM SHOP_CTRT_HIS
WHERE SHOP_NO = :shop
AND :today BETWEEN STA_DT AND END_DT;
-- [Q2] 월 가맹 수수료 정산: 주문 당시 계약의 수수료율로 (월 1회)
SELECT o.SHOP_NO, h.CTRT_TP, h.FEE_RT, COUNT(*) AS ord_cnt
FROM ORD o, SHOP_CTRT_HIS h
WHERE o.ORD_DT BETWEEN :v_from AND :v_to
AND h.SHOP_NO = o.SHOP_NO
AND o.ORD_DT BETWEEN h.STA_DT AND h.END_DT
GROUP BY o.SHOP_NO, h.CTRT_TP, h.FEE_RT;
-- [Q3] 가맹 전환 효과: 전환한 매장의 전환 전 30일과 후 30일 주문 수 (보고 기간 하루 50번)
SELECT h.SHOP_NO, h.STA_DT AS CHG_DT,
COUNT(CASE WHEN o.ORD_DT < h.STA_DT THEN 1 END) AS before_30d,
COUNT(CASE WHEN o.ORD_DT >= h.STA_DT THEN 1 END) AS after_30d
FROM SHOP_CTRT_HIS h, ORD o
WHERE h.CHG_RSN = '가맹전환'
AND o.SHOP_NO = h.SHOP_NO
AND o.ORD_DT >= TO_CHAR(TO_DATE(h.STA_DT, 'YYYYMMDD') - 30, 'YYYYMMDD')
AND o.ORD_DT < TO_CHAR(TO_DATE(h.STA_DT, 'YYYYMMDD') + 30, 'YYYYMMDD')
GROUP BY h.SHOP_NO, h.STA_DT;
-- [Q4] 계약 이력 화면: 매장 하나의 전체 이력 (하루 수십 번)
SELECT STA_DT, END_DT, CTRT_TP, OWNER_NM, FEE_RT
FROM SHOP_CTRT_HIS
WHERE SHOP_NO = :shop
ORDER BY STA_DT;
[Q1] Buffers 5 (SHOP_CTRT_HIS_PK 범위 2 + 테이블 3) · [Q4] Buffers 5
[Q2] 2013년 7월 · ORD_X01 범위(42,408건) + 이력 FULL 6 → HASH JOIN · Buffers 1,082 · 0.09초 (월 1회)
[Q3]
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 12 |00:00:00.93 | 22428 |
| 1 | HASH GROUP BY | | 1 | 12 | 12 |00:00:00.93 | 22428 |
|* 2 | HASH JOIN | | 1 | 1250 | 4896 |00:00:00.92 | 22428 |
|* 3 | TABLE ACCESS FULL| SHOP_CTRT_HIS | 1 | 12 | 12 |00:00:00.01 | 6 |
| 4 | TABLE ACCESS FULL| ORD | 1 | 1000K| 1000K|00:00:00.21 | 22422 |
-----------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("O"."SHOP_NO"="H"."SHOP_NO")
filter(("O"."ORD_DT">=TO_CHAR(TO_DATE("H"."STA_DT",'YYYYMMDD')-30,'YYYYMMDD') AND
"O"."ORD_DT"<TO_CHAR(TO_DATE("H"."STA_DT",'YYYYMMDD')+30,'YYYYMMDD')))
3 - filter("H"."CHG_RSN"='가맹전환')