Skip to content
튜닝 연습파티션
PART-023

재전송 주문 MERGE를 빠르게 고친 뒤 같은 주문번호가 두 달 칸에 하나씩 생겼다

중급MERGE버거398 / 700

상황 ​

매장 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는 본사가 매장에 나눠 준 번호라 전 기간에서 유일해야 한다.
  • LOCAL 인덱스 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시대로 바뀌어 온 주문이다.
  • 매월 1일 보관 기간이 지난 칸을 DROP PARTITION으로 정리한다.

원본 SQL ​

sql
-- 석 달 전에 고친 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

실행계획 학습용 예시 · 교재 14화 규모 ​

xplan
[원래 문장 · 석 달 전 · 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 한 행이다

문제 ​

  1. 고친 MERGE가 빨라진 까닭과, 같은 주문번호가 두 칸에 하나씩 생긴 까닭을 ON 절과 PK 정의로 설명하시오. PK가 왜 막지 못했는지도 쓰시오.
  2. 빠르면서도 주문번호가 하나만 남도록 MERGE와 인덱스를 고치고, 이미 생긴 12건을 정리하는 SQL을 작성하시오. 원래 문장이 ORA-14402로 멈추던 문제도 함께 해결하시오.
  3. 그렇게 고쳤을 때의 부작용을 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자