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

주문 칸을 들어내려니 ORA-02266이 나서 주문상세부터 매달 DELETE로 지운다

실전DDL한빛유통420 / 700

상황 ​

한빛유통은 주문을 월별 파티션 T_ORD_P로 옮기고, 보관 기간(24개월)이 지난 달을 칸째 정리하기로 했다. 주문상세 T_ORD_DET는 파티션 없이 그대로 두었고, 주문을 FK로 참조한다. 이관 뒤 첫 월 정리에서 조민우가 주문상세의 그 달 행을 먼저 DELETE로 지우고 주문 칸을 DROP했는데 ORA-02266이 났다. "자식 행은 다 지웠는데 왜 막히죠?" 지금은 두 테이블 모두 DELETE로 지운다. 박팀장: "FK를 잠깐 끄고 하자는 말도 나오는데, 방법 몇 가지를 비교해서 주문상세까지 칸 단위로 정리할 수 있게 설계해 와."

테이블과 인덱스 ​

  • T_ORD_P 주문(파티션 복사본, 문제 설정): T_ORD 100,000행을 PARTITION BY RANGE (ORD_DT) 월별 24칸(P202301 ~ P202412)과 빈 PMAX로 나눠 CTAS로 지었다(STORAGE (INITIAL 64K)). 칸 하나는 약 4,170행, FULL로 약 38블록이다. 이번 달 정리 대상 P202301은 4,163행이다.
  • PK PK_T_ORD_P (ORD_SEQ): 주문상세의 FK가 이 컬럼 하나를 참조하므로 파티션하지 않은 GLOBAL 유니크 인덱스로 만들었다.
  • T_ORD_DET 주문상세: 200,000행 · 896블록(전체 스캔 807) · 컬럼 ORD_SEQ · ORD_DET_NO · ITM_ID · ORD_QTY · UNT_PRC · PK_T_ORD_DET (ORD_SEQ, ORD_DET_NO) · FK_ORD_DET_ORD (ORD_SEQ) REFERENCES T_ORD_P (ORD_SEQ) 활성. 주문 하나에 1 ~ 3건이고, P202301 주문의 상세는 8,331행이다.
  • 운영은 ARCHIVELOG다. 낮에는 주문 화면과 배송 화면이 두 테이블을 고친다. 주문을 DELETE하는 화면이나 배치는 없다(취소는 상태만 바꾼다).

원본 SQL ​

sql
-- 처음 시도 (2025-01-01)
DELETE FROM T_ORD_DET d
 WHERE d.ORD_SEQ IN (SELECT o.ORD_SEQ FROM T_ORD_P o
                      WHERE o.ORD_DT >= DATE '2023-01-01'
                        AND o.ORD_DT <  DATE '2023-02-01');
COMMIT;
ALTER TABLE T_ORD_P DROP PARTITION P202301 UPDATE GLOBAL INDEXES;      -- ORA-02266

-- 지금 (매월 1일 02:00)
DELETE FROM T_ORD_DET d
 WHERE d.ORD_SEQ IN (SELECT o.ORD_SEQ FROM T_ORD_P o
                      WHERE o.ORD_DT >= DATE '2023-01-01'
                        AND o.ORD_DT <  DATE '2023-02-01');
DELETE FROM T_ORD_P
 WHERE ORD_DT >= DATE '2023-01-01'
   AND ORD_DT <  DATE '2023-02-01';
COMMIT;

실행계획 학습용 예시 ​

xplan
[처음 시도]
  DELETE FROM T_ORD_DET ...                     8,331 rows deleted.   Commit complete.
  ALTER TABLE T_ORD_P DROP PARTITION P202301 UPDATE GLOBAL INDEXES
  ORA-02266: unique/primary keys in table referenced by enabled foreign keys

[지금 · ① 주문상세 DELETE]
-----------------------------------------------------------------------------------------------------------------
| Id  | Operation                 | Name      | Starts | E-Rows | A-Rows |      A-Time | Buffers | Pstart | Pstop |
-----------------------------------------------------------------------------------------------------------------
|   0 | DELETE STATEMENT          |           |      1 |        |      0 | 00:00:00.21 |   26114 |        |       |
|   1 |  DELETE                   | T_ORD_DET |      1 |        |      0 | 00:00:00.21 |   26114 |        |       |
|*  2 |   HASH JOIN RIGHT SEMI    |           |      1 |   8340 |   8331 | 00:00:00.05 |     845 |        |       |
|   3 |    PARTITION RANGE SINGLE |           |      1 |   4170 |   4163 | 00:00:00.01 |      38 |      1 |     1 |
|*  4 |     TABLE ACCESS FULL     | T_ORD_P   |      1 |   4170 |   4163 | 00:00:00.01 |      38 |      1 |     1 |
|   5 |    TABLE ACCESS FULL      | T_ORD_DET |      1 |   200K |   200K | 00:00:00.03 |     807 |        |       |
-----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("D"."ORD_SEQ"="O"."ORD_SEQ")
   4 - filter(("O"."ORD_DT">=TO_DATE(' 2023-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND
              "O"."ORD_DT"<TO_DATE(' 2023-02-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')))

[지금 · ② 주문 DELETE]
-----------------------------------------------------------------------------------------------------------------
| Id  | Operation                 | Name      | Starts | E-Rows | A-Rows |      A-Time | Buffers | Pstart | Pstop |
-----------------------------------------------------------------------------------------------------------------
|   0 | DELETE STATEMENT          |           |      1 |        |      0 | 00:00:00.09 |   12655 |        |       |
|   1 |  DELETE                   | T_ORD_P   |      1 |        |      0 | 00:00:00.09 |   12655 |        |       |
|   2 |   PARTITION RANGE SINGLE  |           |      1 |   4170 |   4163 | 00:00:00.01 |      38 |      1 |     1 |
|*  3 |    TABLE ACCESS FULL      | T_ORD_P   |      1 |   4170 |   4163 | 00:00:00.01 |      38 |      1 |     1 |
-----------------------------------------------------------------------------------------------------------------
  (지우는 주문마다 PK_T_ORD_DET 로 자식이 남았는지 확인하고, GLOBAL PK 의 키를 지운다)

[세션 통계 · ① + ② + COMMIT]
  지운 행 12,494 · redo size 약 4.6 MB · undo change vector size 약 2.1 MB · db block changes 약 38,000

[정리 뒤]
  T_ORD_P P202301   0행 · 38블록 그대로       T_ORD_DET 전체 스캔 807 → 807 (지운 자리가 테이블 곳곳에 흩어져 있다)

문제 ​

  1. 자식 행을 다 지웠는데도 ORA-02266이 난 까닭과, 지금 두 DELETE의 비용이 어디서 나오는지 설명하시오.
  2. ⓐ FK를 잠시 끄고 주문 칸을 DROP하는 안, ⓑ 주문상세에도 주문일을 넣어 같은 기준으로 나누는 안, ⓒ 주문상세를 참조 파티션(PARTITION BY REFERENCE)으로 다시 짓는 안을 정합성, 매달의 비용, 부작용으로 비교하시오.
  3. 고른 안의 DDL과 월 정리 SQL을 작성하고, 그 안의 대가를 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자