테마
본사 영업관리팀의 「매장 일 실적 보드」는 고른 달의 200개 매장 × 날짜 칸마다 주문 수, 전일 대비 증감, 월 누적 주문 수, 최근 7일 평균을 보여 준다. 지역 담당 20명과 영업관리팀이 하루 약 300번 연다. 한 번 여는 데 1.2초가 걸리고 DB CPU 상위 SQL 2위다. SH0042 점장이 이렇게 말한다. "리모델링으로 9월 9일부터 11일까지 쉬었는데 보드에 그 사흘이 아예 없어요. 쉰 날도 0으로 보여 주세요." 개발팀이 낸 안은 셋이다. ㉠ 지금 SQL을 두고 인덱스를 더 만든다. ㉡ 분석 함수로 한 번에 계산한다. ㉢ 매장 일 집계 표를 따로 만든다. 박팀장은 "셋을 숫자와 대가로 비교해서 하나를 골라 달라"고 했다.
ORD 주문: 1,000,000행 · 22,422블록 · ORD_DT VARCHAR2(8) · SHOP_NO 매장 200개(매장마다 하루 약 7건) · 주문일 순으로 쌓여 있다.ORD_PK (ORD_NO), ORD_X01 (ORD_DT, ORD_HMS), IX_ORD_SHOP_DT (SHOP_NO, ORD_DT)(지난달 점장 앱 달력 때문에 만들었다. SORT-044). IX_ORD_SHOP_DT에서 한 매장의 한 달(약 210항목)은 리프 한두 블록에 든다.SHOP 매장: 200행 · 5블록. 2013년 9월 주문은 41,100건이고, 주문이 한 건도 없는 매장 × 날짜 칸이 10개 있다(SH0042의 9일 ~ 11일 포함). 그래서 보드의 칸은 6,000개인데 SQL은 5,990줄을 낸다.SELECT d.SHOP_NO, d.ORD_DT, d.ord_cnt,
d.ord_cnt - (SELECT COUNT(*) FROM ORD p
WHERE p.SHOP_NO = d.SHOP_NO
AND p.ORD_DT = TO_CHAR(TO_DATE(d.ORD_DT, 'YYYYMMDD') - 1, 'YYYYMMDD')) AS diff_prev,
(SELECT COUNT(*) FROM ORD c
WHERE c.SHOP_NO = d.SHOP_NO
AND c.ORD_DT BETWEEN SUBSTR(d.ORD_DT, 1, 6) || '01' AND d.ORD_DT) AS cum_cnt,
(SELECT ROUND(COUNT(*) / 7, 1) FROM ORD w
WHERE w.SHOP_NO = d.SHOP_NO
AND w.ORD_DT BETWEEN TO_CHAR(TO_DATE(d.ORD_DT, 'YYYYMMDD') - 6, 'YYYYMMDD')
AND d.ORD_DT) AS avg7
FROM (SELECT SHOP_NO, ORD_DT, COUNT(*) AS ord_cnt
FROM ORD
WHERE ORD_DT BETWEEN :ym || '01' AND :ym || '31'
GROUP BY SHOP_NO, ORD_DT) d
ORDER BY d.SHOP_NO, d.ORD_DT
[2013년 9월]
---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 5990 |00:00:01.21 | 57522 |
| 1 | SORT AGGREGATE | | 5990 | 1 | 5990 |00:00:00.21 | 17970 |
|* 2 | INDEX RANGE SCAN | IX_ORD_SHOP_DT | 5990 | 7 | 41034 |00:00:00.17 | 17970 |
| 3 | SORT AGGREGATE | | 5990 | 1 | 5990 |00:00:00.38 | 19770 |
|* 4 | INDEX RANGE SCAN | IX_ORD_SHOP_DT | 5990 | 110 | 636K|00:00:00.31 | 19770 |
| 5 | SORT AGGREGATE | | 5990 | 1 | 5990 |00:00:00.26 | 19170 |
|* 6 | INDEX RANGE SCAN | IX_ORD_SHOP_DT | 5990 | 48 | 287K|00:00:00.21 | 19170 |
| 7 | SORT GROUP BY NOSORT | | 1 | 6000 | 5990 |00:00:00.02 | 612 |
|* 8 | INDEX SKIP SCAN | IX_ORD_SHOP_DT | 1 | 41100 | 41100 |00:00:00.01 | 612 |
---------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("P"."SHOP_NO"=:B1 AND "P"."ORD_DT"=TO_CHAR(TO_DATE(:B2,'YYYYMMDD')-1,'YYYYMMDD'))
4 - access("C"."SHOP_NO"=:B1 AND "C"."ORD_DT">=SUBSTR(:B2,1,6)||'01' AND "C"."ORD_DT"<=:B3)
6 - access("W"."SHOP_NO"=:B1 AND "W"."ORD_DT">=TO_CHAR(TO_DATE(:B2,'YYYYMMDD')-6,'YYYYMMDD')
AND "W"."ORD_DT"<=:B3)
8 - access("ORD_DT">=:YM||'01' AND "ORD_DT"<=:YM||'31')
filter(("ORD_DT">=:YM||'01' AND "ORD_DT"<=:YM||'31'))
Starts로 설명하고, 쉰 날이 보드에서 빠지는 까닭을 쓰시오.