- 대량 데이터를 처리할 때 버퍼캐시를 경유하는 I/O 메커니즘은 오히려 성능을 떨어뜨릴 수 있다.
- 버퍼캐시를 경유하지 않고 곧바로 데이터 블록을 읽고 쓸 수 있는 기능을 배운다.
일반적인 블록 I/O는 자주 읽는 블록에 대한 반복적인 I/O를 줄임으로써 성능을 높이기 위해 버퍼캐시를 이용하지만, 대량 데이터를 읽고 쓸 때 버퍼캐시를 탐색한다면 성능이 좋지 않다.
-
데이터를 변경할 때도 먼저 블록을 버퍼캐시에서 찾는다. 변경된 블록들을 주기적으로 찾아 데이터 파일에 반영해 준다.
-
버퍼캐시에서 블록을 찾을 가능성이 없다.
- Full Scan으로 수행되어 읽어 들인 대용량 데이터는 재사용성이 낮다.
- 이런 데이터 블록들이 버퍼 캐시를 점유하면 다른 프로그램 성능에도 나쁜 영향이 생긴다.
- Full Scan으로 수행되어 읽어 들인 대용량 데이터는 재사용성이 낮다.
-
건건이 디스크로부터 버퍼캐시에 적재하고서 읽어야 하는 부담이 있다.
병렬 쿼리로 Full Scan을 수행할 때- 병렬 DML을 수행할 때
- Direct Path Insert를 수행할 때
- Temp 세그먼트 블록들을 읽고 쓸 때
- direct 옵션을 지정하고 export를 수행 할 때
- nocache 옵션을 지정한 LOB 컬럼을 읽을 때
병렬 쿼리
select /*+ full(t) parallel(t 4) */ * from big_table t; select /* index_ffs(t big_table_x1) parallel_index(t big_table_x1 4) */ count(*) from big_table t;위 쿼리처럼 parallel 또는 parallel_index 힌트를 사용하면 지정한 병렬도(4) 만큼 병렬 프로세스가 떠서 동시에 작업을 진행한다. 병렬처리 뿐만 아니라 디스크로부터 버퍼캐시에 적재하는 부담도 없어 수십 배 빨라진다.
-
일반적인 INSERT 방식
- 데이터를 입력할 수 있는 블록을 Freelist에서 찾는다.
- Freelist에서 할당받은 블록을 버퍼캐시에서 찾는다.
- 버퍼캐시에 없으면 데이터파일에서 읽어 버퍼캐시에 적재한다.
- INSERT 내용을 Undo 세그먼트에 기록
- INSERT 내용을 REDO 로그에 기록
Freelist: HWM(High-Water Mark) 아래쪽에 있는 블록 중 데이터 입력이 가능한 블록을 목록으로 관리
-
Direct Path Insert 방식 (일반적인 INSERT 방식 보다 빠르다)
- INSERT ... SELECT 문에 append 힌트 사용
- parallel 힌트를 이용해 병렬 모드로 INSERT
- direct 옵션을 지정하고 SQL*Loader(sqlldr)로 데이터 적재
- CTAS(create table ... as select)문 수행
-
Direct Path Insert 방식이 빠른 이유
- Freelist를 참조하지 않고
HWM바깥 영역에 데이터를 순차적으로 입력 - 블록을 버퍼캐시에서 탐색하지 않는다.
- 버퍼캐시에 적재하지 않고, 데이터 파일에 직접 기록한다.
- Undo 로깅을 안 한다.
- Redo 로깅을 안 하게 할 수 있다. (테이블에 nologging 모드로 전환한 상태에서)
alter table t NOLOGGING;
- Freelist를 참조하지 않고
- 성능은 빨라지지만 Exclusive 모드 TM Lock이 걸려 커밋하기 전까지 다른 트랜잭션은 해당 테이블에 DML을 수행하지 못한다. 트랜잭션이 빈번한 주간에 이 옵션을 사용하는 것은 절대 금물.
- HWM 바깥 영역에 입력하므로 테이블에 여유 공간이 있어도 재활용하지 않는다. 여유 공간이 생겨도 이 방식으로 INSERT를 수행하기 때문에 테이블은 사이즈가 줄지 않고 계속 늘어간다.
- Range 파티션 테이블이면 파티션 DROP 방식으로 데이터를 지워줘야한다.
- 비파티션 테이블이면 주기적으로 Reorg 작업을 수행해야한다.
INSERT는 append 힌트로 Direct Path Write 방식으로 유도가 가능하지만, UPDATE와 DELETE는 불가능하기 때문에 병렬 DML로 처리하면된다.
-
병렬 DML 활성화
alter session enable parallel dml;
-
DML 문에 힌트 사용
insert /*+ parallel(c 4) */ into 고객 c select /*+ full(o) parallel(o 4) */ * from 외부가입고객 o; update /*+ full(c) parallel(c 4) */ 고객 c set 고객상태코드 = 'WD' where 최종거래일시 < '20100101'; delete /*+ full(c) parallel(c 4) */ from 고객 c where 탈퇴일시 < '20100101';
두 단계 전략
DML 문에 두 단계 전략을 사용한다.
Consistent모드로 대상 레코드를 찾고Current모드로 추가/변경/삭제한다.
- 병렬 힌트를 제대로 기술했는데, 실수로 병렬 DML을 활성화하지 않은 경우
- 대상을 찾는
Consistent모드에서는 병렬로 진행 - 추가/변경/삭제하는
Current모드에서는 QC가 혼자 담당하므로 병목 발생 - QC(Query Coordinator)는 병렬로 처리할 수 없거나 병렬로 처리하도록 지정하지 않은 작업을 처리
- 대상을 찾는
- 파티션을 이용하면 대량 추가/변경/삭제 작업을 빠르게 처리할 수 있다.
- 파티션 개념
-
파티셔닝(Partitioning)
- 테이블 또는 인덱스 데이터를 특정 컬럼(파티션 키) 값에 따라 별도 세그먼트에 나눠서 저장하는 것
- 예시1) 계절별로 옷을 관리하면 외출 시 필요한 옷을 쉽고 빠르게 찾는다.
- 예시2) 월별, 분기별, 연별로 분할해서 저장해 두면 빠르게 조회할 수 있고, 관리가 쉽다.
- 테이블 또는 인덱스 데이터를 특정 컬럼(파티션 키) 값에 따라 별도 세그먼트에 나눠서 저장하는 것
-
파티션이 필요한 이유
- 관리적 측면: 파티션 단위 백업, 추가, 삭제, 변경 → 가용성 향상
- 성능적 측면: 파티션 단위 조회 및 DML, 경합 또는 부하 분산
-
파티션의 종류
- Range
- 해시
- 리스트
-- 파티션 생성 예제
create table 주문 (주문번호 number, 주문일자 varchar2(8), 고객ID varchar2(5), ...)
partition by range(주문일자) (
partition P2017_Q1 values less than ('20170401'),
partition P2017_Q2 values less than ('20170701'),
partition P2017_Q3 values less than ('20171001'),
partition P2017_Q4 values less than ('20180101')
);- Full Scan 방식으로 조회할 때 전체가 아닌 일부 파티션 세그먼트만 읽고 멈출 수 있어 성능을 크게 향상한다.
클러스터,IOT와 마찬가지로 데이터가 흩어지지 않고 물리적으로 인접하도록 저장하는 클러스터링 기술에 속한다.- 과거 데이터가 저장된 파티션만 백업하고 삭제하는 등 데이터 관리 작업 또한 효율적이고 빠르게 수행할 수 있다.
클러스터: 데이터를 블록 단위로 모아 저장
IOT: 데이터를 정렬된 순서로 저장하는 구조
파티션 테이블에 대한 SQL 향상 원리
- Pruning
- 실행 시점에 조건절을 분석해서 읽지 않아도 되는 파티션 세그먼트를 액세스 대상에서 제외하는 기능
- 인덱스로 액세스할 수 있지만, 파티션 Pruning을 이용한 스캔보다 훨씬 느리다.
-- 파티션 생성 예제
create table 고객 (고객ID varchar2(5), 고객명 varchar2(10), ...)
partition by hash(고객ID) partitions 4;파티션 키 값을 해시 함수에 입력해서 반환받은 값이 같은 데이터를 같은 세그먼트에 저장하는 방식이다.
파티션 개수는 사용자가 결정하고 데이터를 분산하는 알고리즘은 오라클 내부 해시함수가 결정한다.
해시 알고리즘 특성상 등치(=) 조건 또는 IN-List 조건으로 검색할 때만 파티션 Pruning이 작동한다.
-- 파티션 생성 예제
crate table 인터넷매물 (물건코드 varchar2(5), 지역분류 varchar2(4), ...)
partition by list(지역분류) (
partition P_지역1 values ('서울'),
partition P_지역2 values ('경기', '인천'),
partition P_지역3 values ('부산', '대구', '대전', '광주'),
partition P_지역4 values (DEFAULT)
);사용자가 정의한 그룹핑 기준에 따라 데이터를 분할 저장하는 방식이다.
Range 파티션에선 값의 순서에 따라 저장할 파티션이 결정되지만, 리스트 파티션에서는 순서와 상관없이 불연속적인 값의 목록에 의해 결정된다.
- 테이블 파티션
- 비파티션 테이블(Non-Partitioned Table)
- 파티션 테이블(Partitioned Table)
- 인덱스
- 로컬 파티션 인덱스(Local Partitioned Table)
- 테이블 파티션과 인덱스 파티션이 1:1 대응 관계가 되도록 오라클이 자동으로 관리하는 파티션 인덱스
- 글로벌 파티션 인덱스(Global Partitioned Table)
- 로컬이 아닌 파티션 인덱스
- 테이블 파티션과 독립적인 구성을 갖는다.
- 비파티션 인덱스(Non-Partitioned Table)
- 로컬 파티션 인덱스(Local Partitioned Table)
- 파티션별로 별도 인덱스를 만드는 것
- 테이블 파티션 속성을 그대로 상속받는다. (테이블 파티션 키와 인덱스 파티션 키가 같다)
create index 주문_x01 on 주문 (주문일자, 주문금액) LOCAL; --> 로컬 PREFIXED 파티션 인덱스
create index 주문_x02 on 주문 (고객ID, 주문일자) LOCAL; --> 로컬 NON_PREFIXED 파티션 인덱스- 인덱스 파티션을 테이블과 다르게 구성한 인덱스
- 파티션 유형이 다르거나
- 파티션 키가 다르거나
- 파티션 기준값 정의가 다른 경우
- 테이블 파티션 구성을 변경(drop, exchange, split 등)하는 순간 Unusable 상태로 바뀌므로 인덱스를 재생성해 줘야 한다. (그동안 해당 테이블을 사용하는 서비스를 중단해야 함)
create index 주문_x03 on 주문 (주문금액, 주문일자) GLOBAL
partition by range (주문금액) (
partition P_01 values less than (10000),
pratition P_MX values less than (MAXVALUE)
); --> 글로벌 PREFIXED 파티션 인덱스create index 주문_x04 on 주문 (고객ID, 배송일자); --> 비파티션 인덱스- 테이블 파티션 구성을 변경(drop, exchange, split 등)하는 순간 Unusable 상태로 바뀌므로 인덱스를 재생성해 줘야 한다. (그동안 해당 테이블을 사용하는 서비스를 중단해야 함)

파티션 인덱스를 Prefixed와 Nonprefixed로 나눌 수도 있다.
- Prefixed: 인덱스 파티션 키 컬럼이 인덱스 키 컬럼 왼쪽 선두에 위치
- Nonprefixed: 인덱스 파티션 키 컬럼이 인덱스 키 컬럼 왼쪽 선두에 위치하지 않는다.
"Unique 인덱스를 파티셔닝하려면, 파티션 키가 모두 인덱스 구성 컬럼이어야 한다."
- 서비스 중단 없이 파티션 구조를 빠르게 변경하려면, PK를 포함한 모든 인덱스가 로컬 파티션 인덱스이어야 한다.
- 입력/수정/삭제하는 데이터 비중이 5%를 넘는다면, 인덱스를 둔 상태에서 작업하기보다 인덱스 없이 작업한 후에 재생성하는 게 더 빠르다. (인덱스가 DML 성능에 큰 영향을 미치므로)
수정된 값을 갖는 임시 세그먼트를 만들어 원본 파티션과 바꿔치기하는 방식.
테이블이 파티셔닝돼 있고 인덱스도 로컬 파티션이라면 좋은 방식이다.
-
-- 임시 테이블(거래_t)을 생성, 할 수 있다면 nologging 모드로 생성 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';
-
-- 임시 테이블에 원본 테이블과 같은 구조로 인덱스를 생성한다. 할 수 있다면 nologging 모드로 생성한다. create unique index 거래_t_pk on 거래_t (고객번호, 거래일자, 거래순번) nologging; create index 거래_t_x1 on 거래_t(거래일자, 고객번호) nologging; create index 거래_t_x2 on 거래_t(상태코드, 거래일자) nologging;
-
-- 2014년 12월 파티션과 임시 테이블을 Exchange 한다. alter table 거래 exchange partition p201412 with table 거래_t including indexes without validation;
-
-- 임시 테이블 Drop drop table 거래_t
-
-- nologging 모드로 작업한 경우 파티션을 logging 모드로 전환 alter table 거래 modify partition p201412 logging; alter table 거래_pk modify partition p201412 logging; alter table 거래_x1 modify partition p201412 logging; alter table 거래_x2 modify partition p201412 logging;
수천만 건 데이터를 삭제할 때도, 인덱스를 실시간으로 관리하려면 어마어마한 시간이 소요됨
DELTE가 느린 이유
- 테이블 레코드 삭제
- 테이블 레코드 삭제에 대한 Undo Logging
- 테이블 레코드 삭제에 대한 Redo Logging
- 인덱스 레코드 삭제
- 인덱스 레코드 삭제에 대한 Undo Logging
- 인덱스 레코드 삭제에 대한 Redo Logging
- Undo에 대한 Redo Logging
조건절 컬럼 기준으로 파티셔닝돼 있고 인덱스도 다행히 로컬 파티션이라면 간단히 해결이 가능하다.
alter table 거래 drop partition p201412;
-- 11g 부터 가능
alter table 거래 drop partition for('p201412');-
조건절을 만족하는 데이터가 소수인 경우 DELTE 문을 그대로 사용
delete from 거래 where 거래일자 < '20150101' and (상태코드 <> 'ZZZ' or 상태코드 is null);
-
조건을 만족하는 데이터가 대다수인 경우 남길 데이터만 백업했다가 재입력하는 방식 사용
- 임시 테이블(거래_t) 생성, 남길 데이터만 복제
create table 거래_t as select * from 거래 where 상태일자 < '20150101' and 상태코드 = 'ZZZ'
- 삭제 대상 테이블 파티션을 Truncate 한다.
alter table 거래 truncate partition p201412
- 임시 테이브에 복제해 둔 데이터를 원본 테이블에 입력
insert into 거래 select * from 거래_t
- 임시 테이블을 Drop
drop table 거래_t
DELETE(DML): 데이터는 지워지지만 테이블 용량은 줄어 들지 않는다. 원하는 데이터만 지울 수 있다. 삭제 후 잘못 삭제한 것을ROLLBACK을 통해 되돌릴 수 있다.
TRUNCATE(DDL): 용량이 줄어 들고, 인덱스 등도 모두 삭제 된다. 테이블은 삭제하지는 않고, 데이터만 삭제한다. 한꺼번에 다 지워야 한다.COMMIT이므로 삭제 후 절대 되돌릴 수 없다.
DROP(DDL): 테이블 전체를 삭제, 공간, 객체를 삭제한다. 자동COMMIT이므로 삭제 후 절대 되돌릴 수 없다.
대량 데이터를 INSERT 하려면, 인덱스를 Unusable 시켰다가 재생성하는 방식이 더 빠를 수 있다.
- 테이블을 nologging 모드로 전환 (권장)
alter table target_t nologging;- 인덱스를 Unusable 상태로 전환
alter index target_t_x01 unusable;- 대량 데이터를 입력 (Direct Path Insert 권장)
insert /*+ append */ into target_t
select * from source_t;- 인덱스를 재생성한다. (nologging 권장)
alter index target_t_x01 rebuild nologging;- nologging 모드로 작업한 경우 logging 모드로 전환
alter table target_t logging;
alter index target_t x01 logging;- 작업 대상 테이블 파티션을 nologging 모드로 전환 (권장)
alter table target_t modify partition p_201712 nologging;- 작업 대상 테이블 파티션과 매칭되는 인덱스 파티션을 Unusable 상태로 전환
alter index target_t_x01 modify partition p_201712 unusable;- 대량 데이터를 입력 (Direct Path Insert 권장)
insert /*+ append */ into target_t
select * from source_t where dt between '20171201' and '20171231';- 인덱스를 재생성한다. (nologging 권장)
alter index target_t_x01 rebuild partition p_201712 nologging;- nologging 모드로 작업한 경우 logging 모드로 전환
alter table target_t modify partition p_201712 logging;
alter index target_t x01 modify partition p_201712 logging;

