테마
한빛유통은 주문을 월별 파티션 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_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행이다.-- 처음 시도 (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;
[처음 시도]
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 (지운 자리가 테이블 곳곳에 흩어져 있다)
PARTITION BY REFERENCE)으로 다시 짓는 안을 정합성, 매달의 비용, 부작용으로 비교하시오.