테마
매장 POS는 통신 장애가 나면 그날 주문을 밤에 다시 보낸다. 이미 받은 주문이 대부분이고, 처음 오는 주문과 매장 시계 보정으로 주문 시각이 바뀐 주문이 섞여 있다. 본사 야간 배치가 이 재전송분을 MERGE로 주문 테이블에 반영한다.
석 달 전 조민우가 MERGE를 고쳤다. 원래 문장은 한 번에 5만 블록을 넘게 읽었고, 월말 경계의 주문이 오면 가끔 ORA-14402로 멈췄다. 고친 뒤로는 0.1초에 끝나고 에러도 없다. 그런데 이번 분기 매출 대사에서 같은 주문번호가 6월 칸과 7월 칸에 하나씩 있는 주문 12건이 나왔다.
ORD_RANGE 주문(파티션 복사본, 교재 14화): 1,000,000행 · PARTITION BY RANGE (ORD_YM) 월별 25칸(P201201 ~ P201312과 빈 P_DEFAULT). 칸 하나 FULL ≈ 1,980블록이다. 행 이동(ROW MOVEMENT)은 꺼져 있다(기본값).ORD_YM은 SUBSTR(ORD_DT, 1, 6)으로 채우는 파티션 키다. 주문번호 ORD_NO는 본사가 매장에 나눠 준 번호라 전 기간에서 유일해야 한다.ORD_RANGE_PK (ORD_NO, ORD_YM)(칸마다 높이 2) · ORD_RANGE_X01 (ORD_DT, ORD_HMS). GLOBAL 인덱스는 없다.ORD_RESEND 재전송 스테이징(문제 설정): 하룻밤 약 3,000행 · 22블록 · ORD_NO · ORD_DT · ORD_HMS · ORD_YM · SHOP_NO. 이날 3,000건 중 2,950건은 같은 달로 이미 받은 주문, 38건은 처음 오는 주문, 12건은 6월 30일 23시 59분대 주문이 시계 보정으로 7월 1일 0시대로 바뀌어 온 주문이다.DROP PARTITION으로 정리한다.-- 석 달 전에 고친 MERGE (지금)
MERGE INTO ORD_RANGE t
USING ORD_RESEND s
ON (t.ORD_NO = s.ORD_NO AND t.ORD_YM = s.ORD_YM)
WHEN MATCHED THEN UPDATE
SET t.ORD_DT = s.ORD_DT, t.ORD_HMS = s.ORD_HMS, t.SHOP_NO = s.SHOP_NO
WHEN NOT MATCHED THEN INSERT (ORD_NO, ORD_DT, ORD_HMS, ORD_YM, SHOP_NO)
VALUES (s.ORD_NO, s.ORD_DT, s.ORD_HMS, s.ORD_YM, s.SHOP_NO);
-- 원래 문장의 ON 과 SET (석 달 전까지)
-- ON (t.ORD_NO = s.ORD_NO)
-- SET t.ORD_DT = s.ORD_DT, t.ORD_HMS = s.ORD_HMS, t.ORD_YM = s.ORD_YM, t.SHOP_NO = s.SHOP_NO
[원래 문장 · 석 달 전 · 1.12초 · 가끔 ORA-14402: updating partition key column would cause a partition change]
------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Pstart | Pstop |
------------------------------------------------------------------------------------------------------------------
| 0 | MERGE STATEMENT | | 1 | | 0 | 00:00:01.12 | 51012 | | |
| 1 | MERGE | ORD_RANGE | 1 | | 0 | 00:00:01.12 | 51012 | | |
| 2 | VIEW | | 1 | | 3000 | 00:00:01.01 | 47482 | | |
|* 3 | HASH JOIN OUTER | | 1 | 3000 | 3000 | 00:00:01.01 | 47482 | | |
| 4 | TABLE ACCESS FULL | ORD_RESEND | 1 | 3000 | 3000 | 00:00:00.01 | 22 | | |
| 5 | PARTITION RANGE ALL | | 1 | 1000K | 1000K | 00:00:00.62 | 47460 | 1 | 25 |
| 6 | TABLE ACCESS FULL | ORD_RANGE | 24 | 1000K | 1000K | 00:00:00.48 | 47460 | 1 | 25 |
------------------------------------------------------------------------------------------------------------------
3 - access("T"."ORD_NO"(+)="S"."ORD_NO")
[지금 · 0.11초 · 에러 없음]
-----------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Pstart | Pstop |
-----------------------------------------------------------------------------------------------------------------------------------
| 0 | MERGE STATEMENT | | 1 | | 0 | 00:00:00.11 | 12188 | | |
| 1 | MERGE | ORD_RANGE | 1 | | 0 | 00:00:00.11 | 12188 | | |
| 2 | VIEW | | 1 | | 3000 | 00:00:00.06 | 8972 | | |
| 3 | NESTED LOOPS OUTER | | 1 | 3000 | 3000 | 00:00:00.06 | 8972 | | |
| 4 | TABLE ACCESS FULL | ORD_RESEND | 1 | 3000 | 3000 | 00:00:00.01 | 22 | | |
| 5 | PARTITION RANGE SINGLE | | 3000 | 1 | 2950 | 00:00:00.05 | 8950 | KEY | KEY |
| 6 | TABLE ACCESS BY LOCAL INDEX ROWID | ORD_RANGE | 3000 | 1 | 2950 | 00:00:00.04 | 8950 | KEY | KEY |
|* 7 | INDEX UNIQUE SCAN | ORD_RANGE_PK | 3000 | 1 | 2950 | 00:00:00.02 | 6000 | KEY | KEY |
-----------------------------------------------------------------------------------------------------------------------------------
7 - access("T"."ORD_NO"="S"."ORD_NO" AND "T"."ORD_YM"="S"."ORD_YM")
[대사]
SELECT ORD_NO, COUNT(*), MIN(ORD_YM), MAX(ORD_YM) FROM ORD_RANGE GROUP BY ORD_NO HAVING COUNT(*) > 1;
→ 12행 · 모두 201306 / 201307 · 7월 칸 쪽은 재전송 MERGE 가 INSERT 한 행이다