테마
버거원 본사의 매장 화면은 매장 일 집계 테이블(SHOP_DAY)만 읽는다. 매일 밤 전날 하루치를 넣고, 매월 3일 02:00에는 늦게 올라온 POS 주문을 반영하려고 지난달 한 달 치를 다시 맞춘다. 12월분을 다시 맞추는 데 8초 남짓이 걸렸다.
그런데 SH0077 매장의 12월 24일은 테스트 주문 61건을 지웠는데도 화면에 주문 61건이 그대로 나온다. 개발팀은 MERGE로 바꾸자는 쪽과 그 달을 지우고 다시 넣자는 쪽으로 갈렸다. 박팀장은 세 방법을 비교한 근거를 남기라고 했다.
ORD 주문: 1,000,000행 · 22,422블록 · ORD_PK (ORD_NO) · ORD_X01 (ORD_DT, ORD_HMS) · 날짜순 적재 · 2013년 12월 42,408행(하루 약 1,370건, 매장 하루 약 7건)ORD_ITEM 주문상품: 2,472,717행 · 55,265블록 · ORD_ITEM_PK (ORD_NO, ITEM_ID) 높이 3 · ORD_ITEM_X01 (ORD_DT, ORD_HMS) · ORD_DT는 ORD에서 복사해 둔 칸 · 12월분 104,712행SHOP_DAY(매장 일 집계, 이 문제를 위해 둔 테이블): 146,000행 · 524블록 · PK (SHOP_NO, ORD_DT) · ORD_CNT, ITEM_QTY(둘 다 NOT NULL) · 12월분 6,200행(200매장 × 31일)SHOP_DAY를 읽는다. 다른 테이블이 SHOP_DAY를 참조하지는 않는다.-- 매월 3일 02:00 · 지난달 매장 일 집계 다시 맞추기 (늦게 올라온 POS 반영)
-- ① 이미 있는 날은 고친다
UPDATE SHOP_DAY t
SET (t.ORD_CNT, t.ITEM_QTY) =
(SELECT COUNT(DISTINCT o.ORD_NO), SUM(i.ORD_ITEM_QTY)
FROM ORD o, ORD_ITEM i
WHERE o.SHOP_NO = t.SHOP_NO
AND o.ORD_DT = t.ORD_DT
AND i.ORD_NO = o.ORD_NO)
WHERE t.ORD_DT BETWEEN '20131201' AND '20131231'
AND EXISTS (SELECT 1 FROM ORD o
WHERE o.SHOP_NO = t.SHOP_NO AND o.ORD_DT = t.ORD_DT);
-- ② 없던 날은 넣는다
INSERT INTO SHOP_DAY (SHOP_NO, ORD_DT, ORD_CNT, ITEM_QTY)
SELECT o.SHOP_NO, o.ORD_DT, COUNT(DISTINCT o.ORD_NO), SUM(i.ORD_ITEM_QTY)
FROM ORD o, ORD_ITEM i
WHERE o.ORD_DT BETWEEN '20131201' AND '20131231'
AND i.ORD_NO = o.ORD_NO
AND NOT EXISTS (SELECT 1 FROM SHOP_DAY t
WHERE t.SHOP_NO = o.SHOP_NO AND t.ORD_DT = o.ORD_DT)
GROUP BY o.SHOP_NO, o.ORD_DT;
COMMIT;
[① UPDATE 위 · ② INSERT 아래]
-------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------------------------
| 0 | UPDATE STATEMENT | | 1 | | 0 |00:00:06.02 | 420K |
| 1 | UPDATE | SHOP_DAY | 1 | | 0 |00:00:06.02 | 420K |
|* 2 | HASH JOIN SEMI | | 1 | 6200 | 6199 |00:00:00.03 | 1590 |
|* 3 | TABLE ACCESS FULL | SHOP_DAY | 1 | 6200 | 6200 |00:00:00.01 | 524 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED | ORD | 1 | 42408 | 42408 |00:00:00.02 | 1066 |
|* 5 | INDEX RANGE SCAN | ORD_X01 | 1 | 42408 | 42408 |00:00:00.01 | 117 |
| 6 | SORT AGGREGATE | | 6199 | 1 | 6199 |00:00:05.81 | 409K |
| 7 | NESTED LOOPS | | 6199 | 17 | 104K|00:00:05.62 | 409K |
|* 8 | TABLE ACCESS BY INDEX ROWID BATCHED | ORD | 6199 | 7 | 42347 |00:00:03.10 | 236K |
|* 9 | INDEX RANGE SCAN | ORD_X01 | 6199 | 1370 | 8480K|00:00:01.02 | 43393 |
| 10 | TABLE ACCESS BY INDEX ROWID BATCHED | ORD_ITEM | 42347 | 2 | 104K|00:00:02.41 | 174K |
|* 11 | INDEX RANGE SCAN | ORD_ITEM_PK | 42347 | 2 | 104K|00:00:01.20 | 127K |
-------------------------------------------------------------------------------------------------------------------
| 0 | INSERT STATEMENT | | 1 | | 0 |00:00:02.10 | 56877 |
| 1 | LOAD TABLE CONVENTIONAL | SHOP_DAY | 1 | | 0 |00:00:02.10 | 56877 |
| 2 | HASH GROUP BY | | 1 | 6200 | 3 |00:00:02.09 | 56855 |
|* 3 | HASH JOIN | | 1 | 104K| 53 |00:00:02.08 | 56855 |
|* 4 | HASH JOIN ANTI | | 1 | 42408 | 21 |00:00:00.03 | 1590 |
| 5 | TABLE ACCESS BY INDEX ROWID BATCHED | ORD | 1 | 42408 | 42408 |00:00:00.02 | 1066 |
|* 6 | INDEX RANGE SCAN | ORD_X01 | 1 | 42408 | 42408 |00:00:00.01 | 117 |
|* 7 | TABLE ACCESS FULL | SHOP_DAY | 1 | 6200 | 6200 |00:00:00.01 | 524 |
| 8 | TABLE ACCESS FULL | ORD_ITEM | 1 | 2472K| 2472K|00:00:01.71 | 55265 |
-------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
① 2 - access("O"."SHOP_NO"="T"."SHOP_NO" AND "O"."ORD_DT"="T"."ORD_DT")
3 - filter(("T"."ORD_DT">='20131201' AND "T"."ORD_DT"<='20131231'))
5 - access("O"."ORD_DT">='20131201' AND "O"."ORD_DT"<='20131231')
8 - filter("O"."SHOP_NO"=:B1)
9 - access("O"."ORD_DT"=:B2)
11 - access("I"."ORD_NO"="O"."ORD_NO")
② 3 - access("I"."ORD_NO"="O"."ORD_NO")
4 - access("T"."SHOP_NO"="O"."SHOP_NO" AND "T"."ORD_DT"="O"."ORD_DT")
6 - access("O"."ORD_DT">='20131201' AND "O"."ORD_DT"<='20131231')
7 - filter(("T"."ORD_DT">='20131201' AND "T"."ORD_DT"<='20131231'))
① 갱신 6,199행 · ② 입력 3행 · redo 약 1.6 MB · SH0077 12월 24일 ORD_CNT 61 그대로
Starts로 설명하고, SH0077의 12월 24일 행이 왜 그대로 남는지 설명하시오.