데이터베이스 연구실.log

파티션과 대량 DML

파티션 Pruning과 인덱스 구분, EXCHANGE와 DROP PARTITION 및 Direct Path를 이용한 대량 DML

파티셔닝Range 파티션Hash 파티션List 파티션파티션 PruningPstartPstop로컬 인덱스글로벌 인덱스UNUSABLEEXCHANGE PARTITIONDROP PARTITION

#핵심 요약

파티셔닝은 테이블, 인덱스 데이터를 파티션 키 값에 따라 별도 세그먼트로 나눠 저장하는 것임
파티션 단위 백업, 추가, 삭제로 관리가 쉬워지고, 필요한 파티션만 읽는 Pruning으로 I/O가 줌

Range(날짜 등 순서 있는 값, 가장 많이 씀), Hash(해시 함수로 균등 분산, 변별력 좋고 고르게 분포된 컬럼), List(불연속 값 목록을 직접 그룹핑)

로컬 인덱스는 테이블 파티션과 1:1로 대응해 파티션 구조를 바꿔도 자동으로 맞춰짐
글로벌 파티션 인덱스와 비파티션 인덱스는 파티션 구조를 바꾸면 UNUSABLE이 되어 REBUILD해야 함

대량 UPDATE는 수정된 값으로 임시 테이블을 만들어 파티션과 EXCHANGE하고, 대량 DELETE는 DROP PARTITION으로, 대량 INSERT는 해당 인덱스 파티션만 UNUSABLE로 둔 채 Direct Path로 넣은 뒤 그 파티션만 REBUILD함

#개념과 동작 원리

인덱스 구분 설명
로컬 파티션 인덱스 테이블 파티션과 1:1
add/drop/exchange 해도 자동으로 맞춰짐
글로벌 파티션 인덱스 테이블과 다른 기준으로 파티셔닝
테이블 파티션 구조를 바꾸면 UNUSABLE
Prefixed만 지원
비파티션 인덱스 여러 테이블 파티션을 가리키므로 "글로벌 비파티션 인덱스"라고도 함
구조 변경 시 UNUSABLE
Prefixed / Nonprefixed 인덱스 파티션 키 컬럼이 인덱스 선두에 있는지 여부

실습의 중요한 제약: Unique 인덱스를 파티셔닝하려면 파티션 키가 인덱스 컬럼에 포함되어야 함
PK에 파티션 키가 없으면 PK 인덱스를 로컬로 만들 수 없어 파티션 DROP, TRUNCATE, EXCHANGE마다 PK 인덱스가 UNUSABLE이 되므로, 대량 DML을 파티션으로 처리하려면 설계 단계부터 PK에 파티션 키를 넣어야 함

실습의 Pruning 실패 판별: 파티션 키 조건에 함수, 형변환을 쓰거나 다른 컬럼 조건만 있으면 PARTITION RANGE ALL, PARTITION HASH ALL처럼 모든 파티션을 읽고, Pstart/Pstop이 특정 파티션으로 좁혀지지 않음
Hash 파티션은 등치 조건이어야 Pruning됨

실습의 EXCHANGE 설명: 데이터를 물리적으로 복사하지 않고 딕셔너리 정보만 바꾸므로 건수와 무관하게 순식간에 끝남
인덱스가 로컬이어야 하고 임시 테이블 구조(컬럼, 제약)가 대상 파티션과 정확히 같아야 함
실습에서는 UPDATE 비중이 손익분기점(약 5%)을 넘으면 이 방식을 검토하라고 적었음

#필수 명령어 또는 SQL

#세 가지 파티션 만들기

SQL*Plus (실습 계정)객체 또는 설정을 변경함
CREATE TABLE emp_partition2
PARTITION BY RANGE (deptno)
( PARTITION p1 VALUES LESS THAN (20),
  PARTITION p2 VALUES LESS THAN (30),
  PARTITION p3 VALUES LESS THAN (40) )
AS SELECT * FROM emp;

CREATE TABLE 주문_해쉬파티션
( 주문번호 NUMBER, 주문일자 VARCHAR2(8), 고객ID VARCHAR2(5), 주문금액 NUMBER )
PARTITION BY HASH (고객id) PARTITIONS 8;

CREATE TABLE emp_job_p
( empno NUMBER(4) NOT NULL, ename VARCHAR2(10), job VARCHAR2(9), mgr NUMBER(4),
  hiredate DATE, sal NUMBER(7,2), comm NUMBER(7,2), deptno NUMBER(2),
  CONSTRAINT emp_job_p_pk PRIMARY KEY (empno) )
PARTITION BY LIST (job)
( PARTITION p_president VALUES ('PRESIDENT'),
  PARTITION p_manager   VALUES ('MANAGER'),
  PARTITION p_analyst   VALUES ('ANALYST'),
  PARTITION p_salesman  VALUES ('SALESMAN'),
  PARTITION p_clerk     VALUES ('CLERK'),
  PARTITION p_def       VALUES (DEFAULT) );
SQL*Plus (실습 계정)조회만 함
SELECT partition_name, num_rows
  FROM user_tab_partitions
 WHERE table_name = 'EMP_JOB_P';

결과 해석:

DEFAULT 파티션에는 목록에 없는 값(예: job = 'hr')이 들어감
NUM_ROWS는 통계를 모은 뒤 채워짐

실습 비교 튜닝 전 튜닝 후
emp vs emp_partition2, deptno = 10 Buffers 6 PARTITION RANGE SINGLE, Buffers 2
50만 건 비파티션 vs 해시 8개, 고객id = '00010' 건수 Buffers 2082 PARTITION HASH SINGLE, Buffers 276
같은 해시 테이블, 고객id BETWEEN '00010' AND '00020' PARTITION HASH ALL(Pstart 1, Pstop 8), Buffers 2448

실습의 분기별 Range 파티션 예(주문일자 BETWEEN '20170401' AND '20170630')는 비파티션 테이블 Buffers 4, 파티션 테이블 Buffers 27이었음
두 실행계획 모두 A-Rows 50으로 첫 Fetch에서 멈춘 상태라 전체 조회 비교가 아님
Pstart/Pstop을 보려면 실습에서는 DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'allstats last +partition')을 썼음

#파티션 DROP과 인덱스 상태

SQL*Plus (실습 계정)객체 또는 설정을 변경함
CREATE INDEX 주문_x01 ON 주문 (주문일자, 주문금액) LOCAL;      -- 로컬 Prefixed
CREATE INDEX 주문_x02 ON 주문 (고객id, 주문일자) LOCAL;        -- 로컬 Nonprefixed
CREATE INDEX 주문_x03 ON 주문 (주문금액, 주문일자) GLOBAL
  PARTITION BY RANGE (주문금액)
  ( PARTITION p_1  VALUES LESS THAN (10000),
    PARTITION p_mx VALUES LESS THAN (MAXVALUE) );              -- 글로벌 Prefixed
CREATE INDEX 주문_x04 ON 주문 (고객id, 배송일자);              -- 비파티션
SQL*Plus (실습 계정)데이터 또는 파일을 삭제함, 되돌릴 수 없음
ALTER TABLE 주문 DROP PARTITION P2017_Q1;
SQL*Plus (실습 계정)조회만 함
SELECT index_name, status FROM user_indexes WHERE table_name = '주문';

SELECT index_name, partition_name, status
  FROM user_ind_partitions
 WHERE index_name = '주문_X03'
 ORDER BY partition_name;
SQL*Plus (실습 계정)객체 또는 설정을 변경함
ALTER INDEX 주문_X03 REBUILD PARTITION p_1;
ALTER INDEX 주문_X03 REBUILD PARTITION p_mx;

결과 해석: 로컬 인덱스(x01, x02)는 그대로 쓸 수 있고, 글로벌(x03)과 비파티션(x04)은 UNUSABLE이 되어 그 인덱스를 쓰는 조회가 실패함(오류 메시지 확인 불가)
실습에서는 DROP PARTITION ... UPDATE INDEXES로 인덱스를 함께 유지하는 방법도 실습했음
비파티션 인덱스 x04의 재생성 문장은 기재 없음

#대량 UPDATE: EXCHANGE

실습 환경: 거래 500만 건, Range 파티션 P201412, P9999MX, 로컬 인덱스 거래_pk(Unique), 거래_x1, 거래_x2(상태코드)
일반 UPDATE(약 320만 건 대상)는 2분 31초였음

SQL*Plus (실습 계정)객체 또는 설정을 변경함
CREATE TABLE 거래_t NOLOGGING AS SELECT * FROM 거래 WHERE 1 = 2;

INSERT /*+ append */ INTO 거래_t
SELECT 고객번호, 거래일자, 거래순번,
       CASE WHEN 상태코드 <> 'ZZZ' THEN 'ZZZ' ELSE 상태코드 END
  FROM 거래
 WHERE 거래일자 < '20150101';
COMMIT;

CREATE UNIQUE INDEX 거래_t_pk ON 거래_t (고객번호, 거래일자, 거래순번) NOLOGGING;
CREATE INDEX 거래_t_x1 ON 거래_t (거래일자, 고객번호) NOLOGGING;
CREATE INDEX 거래_t_x2 ON 거래_t (상태코드) NOLOGGING;

ALTER TABLE 거래 EXCHANGE PARTITION p201412 WITH TABLE 거래_t
  INCLUDING INDEXES WITHOUT VALIDATION;
SQL*Plus (실습 계정)데이터 또는 파일을 삭제함, 되돌릴 수 없음
DROP TABLE 거래_t;
SQL*Plus (실습 계정)객체 또는 설정을 변경함
ALTER TABLE 거래 MODIFY PARTITION p201412 LOGGING;
ALTER INDEX 거래_pk MODIFY PARTITION p201412 LOGGING;
ALTER INDEX 거래_x1 MODIFY PARTITION p201412 LOGGING;
ALTER INDEX 거래_x2 MODIFY PARTITION p201412 LOGGING;

결과 해석:

실습에서는 임시 테이블에 먼저 넣고 UPDATE하는 방법(1분 9초)을 거쳐, INSERT할 때부터 바꾼 값으로 넣는 위 방식으로 정리했음
EXCHANGE 방식의 소요 시간은 기재 없음
WITHOUT VALIDATION은 임시 테이블 행이 파티션 범위에 맞는지 검사하지 않으므로, 임시 테이블에 범위 밖 행이 없도록 넣는 쪽이 책임짐
NOLOGGING으로 만든 부분은 백업 전까지 매체 복구로 되살릴 수 없다는 점도 기재 없음

실습에서는 공간을 늘리려고 SYSTEM, UNDO, USERS 테이블스페이스에 데이터 파일을 추가했음

#대량 DELETE: DROP PARTITION

SQL*Plus (실습 계정)데이터 또는 파일을 삭제함, 되돌릴 수 없음
-- 튜닝 전: 행 단위 Undo/Redo, 인덱스 리프 삭제 (실습: 20만 건 중 68902건)
DELETE FROM 거래이력 WHERE 거래일자 < '20150101';
ROLLBACK;

-- 튜닝 후
ALTER TABLE 거래이력 DROP PARTITION P201412;

결과 해석:

퀴즈의 요건: 삭제 기준 컬럼이 파티션 키와 같고, 파티션 단위와 삭제 주기가 맞고, 인덱스가 모두 로컬이어야 함
연습 문제(알림발송이력)의 조건은 발송일자 < TO_DATE('20240228', 'RRRRMMDD')인데 답은 1월 파티션(P202401)만 DROP해 2월 1일~27일 데이터가 남음
또 VARCHAR2 컬럼을 DATE와 비교함

#대량 INSERT: 인덱스 파티션만 UNUSABLE

SQL*Plus (실습 계정)객체 또는 설정을 변경함
ALTER TABLE target_t MODIFY PARTITION P201712 NOLOGGING;
ALTER INDEX target_t_x01 MODIFY PARTITION P201712 UNUSABLE;

INSERT /*+ append */ INTO target_t
SELECT * FROM source_t WHERE 주문일자 BETWEEN '20171201' AND '20171231';
COMMIT;

ALTER INDEX target_t_x01 REBUILD PARTITION P201712 NOLOGGING;
ALTER TABLE target_t MODIFY PARTITION P201712 LOGGING;
ALTER INDEX target_t_x01 MODIFY PARTITION P201712 LOGGING;

결과 해석: 일반 INSERT(30만 건)는 약 4초였음(튜닝 후 시간은 확인 불가)
해당 인덱스 파티션만 UNUSABLE/REBUILD하므로 다른 파티션 서비스에는 영향이 없음

#비파티션 테이블: 제약과 인덱스 해제

SQL*Plus (실습 계정)객체 또는 설정을 변경함
ALTER TABLE target MODIFY CONSTRAINT target_pk DISABLE DROP INDEX;
ALTER INDEX target_x1 UNUSABLE;

INSERT /*+ append */ INTO target SELECT * FROM source;
COMMIT;

ALTER TABLE target MODIFY CONSTRAINT target_pk ENABLE NOVALIDATE;
ALTER INDEX target_x1 REBUILD ONLINE;

결과 해석:

실습 기록: 1000만 건(10행 × 100만) 입력이 제약과 인덱스를 둔 채 3분 51초, 해제 후 29초
ENABLE NOVALIDATE는 기존 데이터를 검사하지 않으므로(제약조건) 연습 문제가 요구한 "정합성 보장"을 이 문장만으로 충족하는지는 수업 자료에 근거가 없음

SEO_HKabsumveritas@gmail.comBuilt with Claude Code & Codex

마지막 갱신 개인 학습 기록이며 운영 환경 적용 전 공식 문서 확인 필요

두 글자 이상 입력, 위아래 화살표로 이동하고 Enter로 열기