테마
14화 · 파고들기
필요할 때 찾아보는 문서다. 처음 읽을 때는
1_사건.md만으로 충분하다. 이 문서의 숫자는 모두 실측값이다.ora-hanorder(13화 종료 상태)의 복사본(_C14·_P14)과ora-burger에서 쟀고, 실습 스크립트 번호를 함께 적는다. 경과 시간(초)은 그날 장비 사정을 탄다. 판정은 redo, 블록 변경, Buffers로 하고, 초는 보조로만 읽는다.
| 찾는 것 | 바로 가기 |
|---|---|
| DML이 행마다 치르는 다섯 가지 (인덱스 · redo/undo · 제약 · 트리거 · HWM) | §1 부수비용 |
| 적재 청구서 한 장: 02·04·08·09·10·12화가 넘긴 것 | §2 청구서 |
무기 넷: 집합 처리 · APPEND · NOLOGGING · 인덱스 끄고 켜기 | §3 무기 |
APPEND의 대가: 배타 락 · 빈 칸 무시 · 꺼지는 조건 · ORA-12838 · 8MB | §4 대가 |
NOLOGGING과 로그 모드: 연습 DB에서는 왜 차이가 없나 | §5 NOLOGGING |
| 파티션 유형 | §6 유형 |
Pruning: Pstart/Pstop 읽는 법 · 키를 가공하면 | §7 Pruning |
| LOCAL Prefixed · Nonprefixed · GLOBAL · 파티션 정리와 인덱스 | §8 인덱스 |
| 파티션의 크기: 첫 익스텐트 8MB · 버거원 3GB 해부 | §8.5 크기 |
| 병렬: QC · Granule · TQ · PQ_DISTRIBUTE · Downgrade · Free에서 본 것 | §9 병렬 |
| P1 리비전 · 정합성 | §10 P1 |
월말 정리: DELETE · SHRINK · DROP PARTITION | §11 월말 |
| 측정값 전부 | §12 측정 |
| Free에서 못 잰 것 | §13 한계 |
| 원천 좌표 | §14 좌표 |
1. DML의 부수비용 다섯
행 하나를 넣는 일 자체는 싸다. 비싼 것은 그 행에 따라붙는 일들이다.
| # | 부수비용 | 무엇을 하나 | 이 화 실측 (5만 건 INSERT · 실습/01) |
|---|---|---|---|
| 1 | 인덱스 유지 | 인덱스마다 정렬된 자리를 찾아 키를 끼운다. 자리가 없으면 리프 블록을 쪼갠다 | 인덱스 1개당 redo 약 12MB · 블록 변경 약 10만(행당 2) |
| 2 | redo · undo | 바꾼 내용(redo)과 바꾸기 전 내용(undo)을 둘 다 적는다(← 13화) | 인덱스 0개 redo 2.44MB · undo 0.13MB → 3개 redo 38.55MB · undo 13.31MB |
| 3 | 제약 | FK는 행마다 부모 키를 찾아간다. CHECK는 행마다 식을 계산한다 | FK 하나: redo 그대로 · 논리 읽기 453,243 → 547,221(행당 약 +2) |
| 4 | 트리거 | 행 트리거는 행마다 PL/SQL을 호출한다. 그리고 여러 행을 한 번에 쓰던 INSERT가 한 행씩 쓰게 된다 | 행 트리거를 붙이면: redo 38.55 → 50.95MB · 블록 변경 301,707 → 403,838(인덱스 하나를 더 붙인 것과 비슷) |
| 5 | HWM | 일반 INSERT는 빈 칸을 찾아 쓰고, APPEND는 고수위선 위에 쓴다(← 05화) | DELETE 33만 건 뒤 되채우기: 일반 6,016블록 · APPEND 7,990블록 |
3번과 4번은
APPEND를 꺼 버린다. 대상 테이블에 FK(자식 쪽)나 트리거가 있으면 직접 경로가 조용히 일반 INSERT로 바뀐다(§4.3).
2. 적재 청구서 — 한 장으로 정산
2.1 인덱스 개수별 (T_ORD_BIG 복사본 · 5만 건 INSERT · 워밍업 2회 뒤 · 실습/01)
| 인덱스 | 생긴 화 | redo | undo | 블록 변경 | 논리 읽기 | 경과(보조) |
|---|---|---|---|---|---|---|
| 0개 | — | 2.44MB | 0.13MB | 1,921 | 2,763 | 0.16초 |
1개 (ORD_DT) | 02화 | 14.13MB | 4.34MB | 102,481 | 153,739 | 3.2초 |
2개 +(ORD_DT, ORD_AMT) | 02화 커버링 | 26.09MB | 8.72MB | 201,060 | 301,956 | 13.8초 |
3개 +(ORD_DT, ORD_SEQ) | 09화 포스트잇 | 38.55MB | 13.31MB | 301,704 | 453,230 | 22.6초 |
4개 +(CUS_ID, ORD_AMT) | 08화 기각안 | 50.62MB | 17.79MB | 397,312 | 596,977 | 52.0초 |
(T2-002) 3개에서 (ORD_DT)만 뺀 2개 (DT_AMT, DT_SEQ) | 04화 승인 대기 | 26.85MB | 9.10MB | 201,131 | 302,059 | 11.4초 |
- 04화에서 잰 0개, 1개, 2개 값(2.44 · 14.13 · 26.10MB)이 거의 그대로(26.09) 다시 나온다. redo는 회차가 바뀌어도 소수점 둘째 자리까지 거의 같다. 경과 시간은 리두 로그 교체 대기에 좌우되어 회차마다 크게 흔들린다.
- 0 → 3개: redo 15.8배 · 블록 변경 157배 · 논리 읽기 164배.
2.2 화별로 넘어온 청구서
| 넘긴 화 | 넘긴 것 | 이 화에서 정산한 값 |
|---|---|---|
| 02 | 인덱스 2개의 적재 비용 | §2.1의 2개 줄 · 100만 건 재적재 [A]의 몸통 |
| 03 | 인덱스 1개가 늘린 redo(ora-dist) | 같은 원리: T_ORD_BIG에서 인덱스 1개당 redo 약 12MB / 5만 건 |
| 04 | 05_DML_청구서.sql 1차 증거 · IX_ORD_BIG_DT 폐기 승인 대기 | §2.1 재측정 · T2-002(§2.1 마지막 줄) |
| 05 | 인덱스 2개 48MB(테이블 47MB보다 크다) · 월말 DELETE | 인덱스 3개 75MB(22 + 26 + 27) · §11 |
| 08 | 기각한 커버링 (CUS_ID, ORD_AMT) | §2.1의 4개 줄: 만들었다면 redo +12.07MB |
| 09 | 인덱스 3개의 적재 비용 | §2.1의 3개 줄 |
| 10 | 인덱스 3개를 단 채 일반 INSERT로 100만 건을 넣으면 오래 걸린 이유 · 통계 수집 단계와 시간을 다툰 진짜 원인 | §2.3: 329초 중 309.5초가 리두 로그 교체 대기 · 통계 수집은 1.06초 |
| 12 | FK 인덱스 · 인덱스 맨 오른쪽 블록 | §2.4: 순번 키 인덱스, 보통 vs 역순 |
2.3 100만 건 재적재 — 운영 그대로 (실습/02 [A])
| 단계 | redo | undo | 블록 변경 | 논리 읽기 | 경과 | 그중 로그 교체 대기 |
|---|---|---|---|---|---|---|
| ② 적재 + 커밋 (일반 INSERT · 인덱스 3개) | 916.6MB | 344.4MB | 5,455,266 | 7,949,703 | 329초 | 309.5초 |
| ④ 통계 수집 | 1.4MB | 0 | 15,617 | 37,327 | 1.06초 | 0 |
- 경과 시간의 94%, 98%가 기다림이다(2회: 309.5 / 329초, 532.2 / 542초). 연습 DB의 온라인 리두 로그는 10MB × 2다. 916.6MB를 쓰려면 10MB짜리 로그를 아흔 번 넘게 갈아 끼워야 하고, 갈아 끼울 때마다 앞 로그에 적힌 변경 블록이 아직 디스크에 안 내려갔으면 멈춘다. 이 대기가
log file switch (checkpoint incomplete)다. - 10화는 같은 배치를 9분 13초(다른 작업과 겹친 날은 22분 46초)로 쟀다. 이 화에서는 329초, 542초(2회, 검증 실행은 585초)였고 redo는 916.6MB, 916.1MB였다. 시간은 흔들려도 redo는 흔들리지 않는다.
- 켠 채 적재한 인덱스는 같은 100만 행인데도 크기가 이렇게 다르다.
| 인덱스 | 갓 만든 것 (CREATE INDEX) | 켠 채 일반 INSERT로 채운 것 | 끄고 켜기(REBUILD) |
|---|---|---|---|
(ORD_DT) | 2,653 | 4,374 · 4,369(+65%) | 2,653 |
(ORD_DT, ORD_AMT) | 3,206 | 3,555(+11%) | 3,206 |
(ORD_DT, ORD_SEQ) | 3,344 | 5,505 · 5,373(+61~65%) | 3,344 |
무작위 순서로 끼워 넣으면 리프가 반씩 쪼개지면서(50-50 분할) 반쯤 빈 블록이 남는다. (ORD_DT, ORD_AMT)가 덜 커진 것은 같은 날짜 안에서 금액이 흩어져 있어 분할 위치가 달라지기 때문이다.
2.4 인덱스 맨 오른쪽 블록 — 보통 vs 역순 키 (실습/01 마지막 · ← 12화)
순번(ORD_SEQ) 5만 개를 기존 값보다 큰 값으로, 한 세션에서 넣는다.
| 인덱스 | redo | 리프 분할 | 그중 90-10 분할 | 블록 변경 | 범위 조건 BETWEEN |
|---|---|---|---|---|---|
보통 (ORD_SEQ) | 5.96MB | 107 | 107 | 5,305 | INDEX RANGE SCAN |
역순 (ORD_SEQ) REVERSE | 14.19MB | 0 | 0 | 102,904 | INDEX FULL SCAN · 2,230블록(100행) |
- 보통 인덱스는 새 키를 맨 오른쪽 리프 한 곳에 몰아 넣는다. 세션이 하나뿐이면 오히려 가장 싸다. 꽉 찬 블록을 90-10으로 쪼개 거의 빈칸 없이 이어 붙이기 때문이다.
- 문제는 여러 세션이 동시에 넣을 때 그 한 블록에 몰린다는 점이다(12화 실측:
buffer busy waits·enq: TX - index contention). - 역순 키는 몰림을 흩는 대신 키마다 다른 블록을 고치므로 블록 변경이 19배, redo가 2.4배로 늘고, 범위 검색을 잃는다(
BETWEEN이 찾아가지 못하고 인덱스 전체를 훑는다). - 다른 처방(해시 파티션 인덱스로 오른쪽 끝을 N개로 나누기, 키 앞에 분산 값 붙이기)도 대가의 구조는 같다. 흩는 만큼 범위 검색과 정렬을 잃는다. 다중 세션 경합의 크기는 Free의 CPU 2개로는 의미 있게 잴 수 없다(§13).
3. 무기 넷
3.1 같은 100만 행, 넣는 법만 다르다 (실습/02 [A] · 실습/03)
| 넣는 법 | 적재 redo | 적재 undo | 적재 블록 변경 | 적재 경과 | 인덱스 재생성 | 합계 경과 |
|---|---|---|---|---|---|---|
| [A] 일반 INSERT · 인덱스 켠 채 | 916.6MB | 344.4MB | 5,455,266 | 329초 | — | 329초 |
[B] APPEND · 켠 채 | 268.8MB | 126.66MB | 205,171 | 8.3초 | — | 8.3초 |
| [E] 일반 INSERT · 끄고 → 켜기 | 48.4MB | 2.61MB | 45,383 | 1.05초 | 0.4MB · 5.08초 | 6.1초 |
[C] APPEND · 끄고 → 켜기 | 0.2MB | 0.04MB | 1,872 | 0.26초 | 0.4MB · 4.74초 | 5.0초 |
[D] [C] + NOLOGGING | 0.2MB | 0.04MB | 1,870 | 0.22초 | 0.4MB · 4.71초 | 4.9초 |
2회차: [B] 269.1MB · 11.0초 / [E] 48.4 + 0.4MB · 1.1 + 4.6초 / [C] 0.2 + 0.4MB · 0.24 + 4.71초 / [D] 0.2 + 0.4MB. redo는 같고 초만 흔들린다. 02의 [A]는 2회차에 916.1MB · 542초였다.
표에서 읽을 것:
- [A] → [E]: 인덱스를 끄기만 해도 329초 → 6.1초. 329초의 몸통은 테이블이 아니라 인덱스였다.
- [A] → [B]: 직접 경로만으로는 268.8MB가 남는다. 테이블 몫은 직접 경로로 덜 수 있지만, 켜져 있는 인덱스는 적재가 끝날 때 한꺼번에 고쳐 넣으면서 redo와 undo를 만든다.
- [C]는 두 무기를 함께 쓴 것이다. 테이블은 직접 경로로 넣고, 인덱스는 다 넣은 뒤 정렬해서 새로 짓는다. 재생성한 인덱스는 갓 만든 크기다(2,653 · 3,206 · 3,344).
- [C] ≈ [D]. 이 연습 DB가
NOARCHIVELOG라서다(§5).
3.2 무기별 정리
| 무기 | 어떻게 | 무엇을 덜어 주나 | 대가 |
|---|---|---|---|
| 집합 처리 | 루프를 INSERT … SELECT, MERGE, UPDATE … 한 문장으로 바꾼다 | 행마다 생기는 왕복 · 커밋 · 행 단위 redo/undo(P1: redo 28.66 → 4.47MB) | 중간 커밋이 없어지므로 언두가 한 트랜잭션에 쌓인다(13화 ORA-30036이 나던 자리) · 중간에 실패하면 전부 롤백 |
APPEND(직접 경로) | INSERT /*+ APPEND */ … SELECT | 버퍼 캐시를 거치지 않고 블록째 쓴다 · 데이터 undo 거의 0 · (NOARCHIVELOG이거나 NOLOGGING이면) 데이터 redo 거의 0 | §4 전체 |
NOLOGGING | 테이블 · 인덱스 속성. 직접 경로 연산에만 효과가 있다 | ARCHIVELOG DB에서 직접 경로의 데이터 redo | 그 블록은 복구할 수 없다. 적재 뒤 백업이 필수다. FORCE LOGGING DB에서는 무시된다 |
| 인덱스 끄고 켜기 | ALTER INDEX … UNUSABLE → 적재 → ALTER INDEX … REBUILD | 행마다 하던 인덱스 유지 · 인덱스 비대(+65% → 0) | 꺼진 동안 그 인덱스를 쓰던 조회가 조용히 다른 길로 간다(Q1 91 → 5,822) · UNIQUE/PK 인덱스는 끌 수 없다(DML이 ORA-01502) · 재생성 시간 |
집합 처리로 바꿀 수 있는지부터 따진다. 한 문장으로 못 바꾸는 행 단위 로직(행마다 다른 분기, 함수 호출)이 남는다면, 그 비용이 어디서 생기는지는 15화에서 잰다.
4. APPEND의 대가
4.1 배타 락 (실습/04 [1])
| 커밋 전, 내 세션의 TM 락 | LMODE | 다른 트랜잭션이 같은 테이블에 INSERT |
|---|---|---|
| 일반 INSERT | 3 (ROW EXCLUSIVE) | 된다 |
APPEND | 6 (EXCLUSIVE) | ORA-00054(NOWAIT로 물었을 때) · NOWAIT가 없으면 커밋할 때까지 기다린다 |
배치가 마감 전에 끝나는 한 아무도 모른다. 마감을 넘기면 낮 업무가 그 테이블에 쓰지 못한다. 이 내용을 T3 「동시 실행 영향」 칸에 적는다.
4.2 빈 칸 무시 — 고수위선 위에만 쓴다 (실습/04 [3] · ← 05화)
T_ORD_BIG 복사본 · 2022년 33만 건 DELETE 뒤, 같은 행을 다시 넣는다 | 고수위선 아래 블록 | 전체 스캔 Buffers |
|---|---|---|
DELETE 직후 | 5,915 | — |
| 일반 INSERT로 되채움 | 6,016 | 5,889 |
APPEND로 되채움 | 7,990 | 7,862 |
월말 DELETE와 일상 APPEND를 함께 쓰는 테이블은 매달 한 달치씩 커진다. 05화의 「SHRINK를 매달」 처방도 그만큼 더 비싸진다.
4.3 APPEND가 꺼지는 조건 (실습/01 [01-T1~T3])
실행계획의 첫 줄과 Note를 본다. 에러는 나지 않는다.
| 대상 테이블 | 계획 | Note |
|---|---|---|
| FK(자식) + 행 트리거 | LOAD TABLE CONVENTIONAL | Direct Load disabled because triggers are defined |
| FK(자식)만 | LOAD TABLE CONVENTIONAL | Direct Load disabled because parent referential constraints are present |
| 둘 다 없음 | LOAD AS SELECT + OPTIMIZER STATISTICS GATHERING | — |
확인하는 습관:
APPEND를 붙였으면 실행계획에서LOAD AS SELECT를 눈으로 확인한다. 힌트를 붙였다고 해서 직접 경로로 돌았다는 증거가 되지는 않는다.
4.4 ORA-12838 — 이 버전에서는 나지 않는다 (실습/04 [2])
원천(성능고도화 II권)과 19c까지의 문서는 "직접 경로로 넣은 트랜잭션은 커밋 전에 그 테이블을 읽거나 고칠 수 없다(ORA-12838)"고 설명한다. 그런데 이 연습 DB(Oracle AI Database 26ai Free 23.26)에서는 APPEND 직후 같은 트랜잭션의 SELECT COUNT(*)가 1,001,000을 그대로 돌려줬다. 운영 버전이 다르면 책대로 막힌다. 제약도 내 버전에서 재 보고 쓴다.
4.5 온라인 통계 — 히스토그램은 모으지 않는다 (실습/03 [B])
빈 테이블(TRUNCATE 직후)에 직접 경로로 넣으면 23ai는 적재하면서 통계를 모은다(실행계획의 OPTIMIZER STATISTICS GATHERING 줄, NOTES = STATS_ON_LOAD). 테이블 행수와 인덱스 통계까지 새로 갱신된다. 그러나 열 통계를 보면 다르다.
| 열 | 원본 T_ORD_BIG | APPEND 재적재 직후 |
|---|---|---|
ORD_ST | FREQUENCY(← 10화 정책서 1호) | NONE · STATS_ON_LOAD |
ORD_DT | NONE | NONE · STATS_ON_LOAD |
"APPEND로 바꾸면 통계 수집 단계를 빼도 된다"는 틀린 결론이다. 히스토그램을 정책으로 걸어 둔 테이블은 적재 뒤 수집 단계를 남겨야 한다(연습 DB에서 1.06초). 10화 「통계 수집 정책서」 P3에 덧붙일 사실이다.
4.6 파티션의 첫 익스텐트를 비워 둔다 (실습/04 [4] · 실습/10)
이 화에서 가장 큰 함정이다. 자세한 내용은 §8.5에 있다.
5. NOLOGGING과 로그 모드
이 연습 DB (NOARCHIVELOG) | 운영 (보통 ARCHIVELOG) | |
|---|---|---|
| 일반 INSERT | redo 전부 | redo 전부 |
직접 경로(APPEND) · LOGGING 테이블 | 데이터 redo 거의 없음(실측 0.2MB / 100만 행) | 데이터 redo 전부 |
직접 경로 · NOLOGGING 테이블 | 위와 같다(실측 0.2MB, 차이 없음) | 데이터 redo 거의 없음 · 그 블록은 미디어 복구 불가 |
CREATE INDEX · REBUILD | redo 거의 없음(실측 0.4MB / 3개) | 인덱스 크기만큼 · NOLOGGING이면 거의 없음 |
FORCE LOGGING (DB · 테이블스페이스) | — | NOLOGGING을 무시하고 전부 기록(Data Guard 환경) |
- 이 DB의 상태:
SELECT log_mode, force_logging FROM v$database→ NOARCHIVELOG · NO. - 그래서 [C]와 [D]가 같다. 연습 DB의 0.2MB를 운영의 기대값으로 옮기면 안 된다. T3의 「환경」 칸에 로그 모드를 적는 이유가 여기에 있다.
ARCHIVELOG로 바꿔서 재려면 인스턴스를 재기동해야 하므로 이 교재에서는 하지 않는다(§13).
6. 파티션 유형
| 유형 | 키 | 쓰는 곳 | 이 화에서 |
|---|---|---|---|
| Range | 값의 구간(VALUES LESS THAN) | 날짜 · 월별 보관 · 오래된 구간 정리 | T_ORD_BIG_P14(주문일 월별 36 + PMAX) · T_ORD_LOG_P14 · 버거원 ORD_RANGE |
| Interval | Range + 새 구간 자동 생성 | 다음 달 파티션을 미리 안 만들어도 됨 | (실험 중 측정만) Pstop이 1048575로 찍힌다. 앞으로 생길 수 있는 칸까지 포함한 값이다 |
| List | 값의 목록(VALUES (…)) | 지역 · 상태 · 코드 | 버거원 ORD_LIST(ORD_YM 25칸) |
| Hash | 키의 해시 | 균등 분산 · 경합 분산 · partition-wise 조인 | 버거원 서브파티션(HASH(ORD_NO) 8) |
| Composite | 두 단계(Range-Hash · List-Hash · Range-List …) | 1단계로 정리, 2단계로 분산 | 버거원 ORD_LIST_HASH(LIST 25 × HASH 8) |
| Reference | 부모 테이블의 파티션을 FK로 물려받음 | 주문 ↔ 주문상세를 같은 칸으로 | — |
| System | 키 없이 애플리케이션이 칸을 지정 | 특수 용도 | — |
라이선스: 파티셔닝은 Enterprise Edition의 유상 옵션이다. Free에는 들어 있어서 이 화의 파티션 실습은 모두 Free에서 했다. 강도현의 2021-03-30 커밋 "파티션은 유상 옵션이라 승인 못 받음"도 이 사정 때문이다. 이번에는 근거 수치(§11)를 붙여 예산을 다시 올린다.
7. Pruning — Pstart / Pstop 판독
Pruning은 조건을 보고 열 필요가 없는 파티션은 아예 열지 않는 것이다. 실행계획의 Pstart, Pstop을 보면 몇 번 칸부터 몇 번 칸까지 열었는지 알 수 있다(DISPLAY_CURSOR(FORMAT => '… +PARTITION')).
7.1 판독표
| 계획 줄 | Pstart / Pstop | 뜻 |
|---|---|---|
PARTITION RANGE SINGLE | 30 / 30 | 30번 칸 하나(정적: 계획을 세울 때 이미 안다) |
PARTITION RANGE ITERATOR | 30 / 32 | 30 ~ 32번 연속 |
PARTITION RANGE ITERATOR | KEY / KEY | 바인드 변수, 서브쿼리, 조인 값으로 실행할 때 고른다(동적) |
PARTITION LIST INLIST | KEY(I) / KEY(I) | IN 목록에 든 칸들 |
PARTITION RANGE ALL · LIST ALL | 1 / N | 전부 연다. Pruning 실패(또는 원래 전부 필요) |
PARTITION HASH ALL 아래 | 1 / 8 | 서브파티션 8조각을 전부 |
(Interval) ALL | 1 / 1048575 | 앞으로 생길 수 있는 칸 번호의 끝까지 |
| (조인 필터) | :BF0000 | 조인 상대에서 만든 블룸 필터로 칸을 고른다(동등 조인 + 해시 조인일 때). 이 화의 달력 조인은 범위 조인이라 NL + KEY로 나왔다 |
7.2 한빛유통 T_ORD_BIG_P14 (주문일 월별 36칸 + PMAX · 실습/07)
| 조건 | 계획 | Pstart/Pstop | Buffers |
|---|---|---|---|
[A] TO_CHAR(ORD_DT,'YYYYMMDD') BETWEEN '20240601' AND '20240630' (Q1 rev00) | RANGE ALL | 1 / 37 | 5,868 |
[B] ORD_DT >= DATE '2024-06-01' AND ORD_DT < DATE '2024-07-01' (Q1 rev01) | RANGE SINGLE | 30 / 30 | 161 |
| [C] 같은 조건 · 바인드 변수 | RANGE ITERATOR | KEY / KEY | 161 |
[D] TRUNC(ORD_DT,'MM') = DATE '2024-06-01' | RANGE ALL | 1 / 37 | 5,868 |
| [E] 6 ~ 8월 | RANGE ITERATOR | 30 / 32 | 493 |
| [F] 달력 테이블이 고른 달 (조인 · 서브쿼리) | RANGE ITERATOR | KEY / KEY | 163 · 165 |
| (기준) 파티션 없는 원본 · Q1 rev02 커버링 | INDEX RANGE SCAN | — | 91 |
- 키를 가공하면 칸을 못 고른다.
TO_CHAR도,TRUNC(…,'MM')도 전부 연다. 칸막이가 월 단위여도 마찬가지다. 인덱스 규칙(02화 §3)과 같다. 컬럼이 가공되지 않은 채로 조건에 와야 한다. - 가공했을 때의
E-Rows는 다시 2,500이다(02화에서 본 그 고정 추정값). - 파티션이 조회를 빠르게 하는 것은 아니다. 6월 한 칸을 통째로 읽은 161은 커버링 인덱스의 91보다 많다. 파티션 덕분에 '한 칸만 열면' 될 뿐, 칸 안은 다시 인덱스 문제다(§8).
7.3 버거원 (ORD_YM = 파티션 키 · 실습/09)
| 조건 (2012년 6월 · 매장별) | 계획 | Pstart/Pstop | Buffers |
|---|---|---|---|
ORD_LIST · ORD_YM = '201206' | LIST SINGLE | KEY/KEY → 6 / 6 | 1,980 |
ORD_LIST · SUBSTR(ORD_DT,1,6) = '201206' | LIST ALL | 1 / 25 | 47,460 |
ORD_LIST · ORD_DT LIKE '201206%'(키가 아닌 컬럼) | LIST ALL + 로컬 인덱스 | 1 / 25(인덱스 24번 시작) | 41,897 |
ORD_RANGE · ORD_YM BETWEEN '201206' AND '201208' | RANGE ITERATOR | 6 / 8 | 5,938 |
ORD_LIST · ORD_YM IN ('201206','201207','201208') | LIST INLIST | KEY(I) | 5,937 |
ORD_LIST_HASH · ORD_YM = '201206' | LIST SINGLE + HASH ALL | 1 / 8(서브) | 9,039 |
(기준) 파티션 없는 ORD · ORD_DT LIKE '201206%' | INDEX RANGE SCAN(CF 22,223) | — | 1,089 |
ORD_DT는 파티션 키가 아니다. 파티션 키는ORD_YM이다. 화면이ORD_DT로 물으면 칸을 못 고르고, 로컬 인덱스로 칸마다 한 번씩 찾아간다. 게다가ORD_LIST는ORD_RANDOM에서 적재돼 CF가 나쁘다(999,024).- 파티션 없는
ORD(잘 정렬된 테이블 · 04화)가 가장 적게 읽었다.
8. 파티션 인덱스 — LOCAL Prefixed · Nonprefixed · GLOBAL
| 종류 | 정의 | 장점 | 대가 |
|---|---|---|---|
| LOCAL Prefixed | 테이블과 같은 칸으로 나뉘고, 파티션 키가 인덱스 선두에 온다 | 키 조건이면 한 칸의 인덱스만 본다 · 칸을 정리(DROP)하면 같이 사라진다 | — |
| LOCAL Nonprefixed | 같은 칸으로 나뉘지만, 파티션 키가 선두가 아니다(또는 없다) | 칸을 정리하면 같이 사라진다 | 조건에 파티션 키가 없으면 칸마다 인덱스를 하나씩 뒤진다 |
| GLOBAL(파티션 안 한 인덱스) | 테이블 전체에 하나 | 키 없이 찾아도 한 번에 찾는다 | 칸을 DROP/TRUNCATE하면 UNUSABLE이 된다. UPDATE GLOBAL INDEXES를 붙여야 산다 |
| GLOBAL 파티션 인덱스 | 인덱스가 테이블과 다른 기준으로 나뉜다(예: 해시) | 오른쪽 끝 블록 분산 · 큰 인덱스 관리 | 칸을 정리할 때 GLOBAL과 같은 문제가 생긴다 |
실측 (실습/07 [G][H] · 실습/09 [3] · 실습/06 [4])
| 조회 | 인덱스 | 계획 | Buffers |
|---|---|---|---|
| 고객 1명 500건 · 날짜 없이 | LOCAL Nonprefixed (CUS_ID) · 37칸 | RANGE ALL · 인덱스 Starts 36(빈 PMAX 제외) | 572(인덱스 72) |
| 같은 고객 · 2024년 6월 | 같은 인덱스 | RANGE SINGLE 30 | 16 |
| 고객 1명 500건 · 날짜 없이 | GLOBAL (CUS_ID) | TABLE ACCESS BY GLOBAL INDEX ROWID | 504(인덱스 4) |
| 버거원 주문번호 하나 | LOCAL Nonprefixed PK (ORD_NO, ORD_YM) · 25칸 | RANGE ALL · 인덱스 Starts 24 | 50 |
같은 주문 + ORD_YM | 같은 PK | RANGE SINGLE 19 · INDEX UNIQUE SCAN | 3 |
(기준) 파티션 없는 ORD | ORD_PK (ORD_NO) | INDEX UNIQUE SCAN | 4 |
| Q1 rev01 · 6월 | LOCAL Prefixed (ORD_DT, ORD_AMT) | 옵티마이저는 6월 칸 FULL을 골랐다 | 161 |
| 같은 Q1 · 로컬 인덱스 강제 | 같은 인덱스 | RANGE SINGLE + 인덱스 | 89(파티션 없는 커버링 91과 거의 같다) |
이력 조회 ORD_SEQ = 4242 · DROP PARTITION 뒤 | GLOBAL → UNUSABLE | RANGE ALL + TABLE ACCESS FULL(에러 없음) | 3,505 |
같은 조회 · UPDATE GLOBAL INDEXES로 정리한 뒤 | GLOBAL · VALID · ORPHANED_ENTRIES = YES | INDEX RANGE SCAN | 3 |
PK나 UNIQUE를 LOCAL로 만들려면 파티션 키가 인덱스에 들어 있어야 한다. 버거원 PK가
(ORD_NO, ORD_YM)인 것도 그래서이고, 같은 이유로 번호만으로 찾으면 nonprefixed 비용(25칸)을 치른다. 23ai는UPDATE GLOBAL INDEXES로 칸을 들어낼 때 인덱스 항목을 바로 지우지 않고 고아 항목(orphaned entries)으로 남겨 두었다가 나중에 정리한다. 그래서 들어내는 순간에는 비용이 적다(redo 0.01MB).
8.5 파티션의 크기 — 첫 익스텐트 8MB
무엇이 일어나나
- 이 버전은 테이블 파티션(서브파티션 포함)의 첫 익스텐트를 8MB(1,024블록)로 잡는다. 파티션 없는 테이블은 64KB부터 시작한다.
- 직접 경로 적재(
INSERT /*+ APPEND */)는 지금 익스텐트의 남은 블록을 '비었음'으로 표시해 둔 채 새 익스텐트부터 쓴다. 그 빈 블록들은 고수위선 아래에 들어간다. - 그래서 빈 칸(또는
TRUNCATE한 칸)에APPEND하면 칸마다 빈 블록을 약 1,006개 안게 된다. 전체 스캔은 고수위선까지 다 읽는다. CTAS는 이렇게 하지 않고 첫 익스텐트부터 채운다.
실측 — 한빛유통 (실습/04 [4] · 6월 한 칸 = 27,390행 · 데이터 블록 160)
| 만드는 법 | 세그먼트 합계 | 6월 칸 빈 블록(fs4) | 6월 전체 스캔 Buffers |
|---|---|---|---|
파티션 · CTAS | 288MB(8MB × 36) | — | 161 |
파티션 · TRUNCATE → APPEND | 576MB | 1,006 | 1,169 |
파티션 STORAGE (INITIAL 64K) · TRUNCATE → APPEND | 72MB | 5 | 169 |
| (비교) 파티션 없음 · 원본 | 47MB | — | (전체 5,826) |
실측 — 버거원 3GB 해부 (실습/09 [1] · 실습/10)
| 테이블 (주문 100만 행) | 세그먼트 | 할당 | 고수위선 아래 블록 | 2012년 6월 전체 스캔 |
|---|---|---|---|---|
ORD(파티션 없음) | 1 | 176MB | 22,422 | — |
ORD_LIST(LIST 25 · 빈 테이블 + APPEND) | 24 | 384MB | 48,276 | 1,980(데이터 971 + 빈 1,006) |
ORD_LIST_HASH(LIST 25 × HASH 8 · 빈 테이블 + APPEND) | 192 | 3,072MB | 223,410 | 9,039(서브마다 데이터 약 121 + 빈 1,006) |
같은 설계 · CTAS | 192 | 1,536MB | 26,792 | 982 |
같은 설계 · CTAS + INITIAL 64K | 192 | 379MB | 25,640 | 998 |
- 3GB의 절반은 칸(192개)마다 8MB씩 잡아 둔 자리이고(읽지는 않는다), 나머지 절반은
APPEND가 비워 둔 빈 블록이다(전체 스캔이 읽는다). 한 달 조회를 9배로 키운 것은 뒤의 절반이다. - 빈 서브파티션(
P_DEFAULT)은 세그먼트가 없다(지연 세그먼트 생성). 그래서 200개가 아니라 192개다. - 같은 문제가
ORD_LIST(24칸)에도 있다. 칸마다 빈 블록이 1,006개라서ORD(22,422)의 두 배가 넘는 48,276이 나왔다.
처방
| 처방 | 언제 |
|---|---|
파티션 테이블에 STORAGE (INITIAL …)를 명시한다(칸 크기에 맞게) | 칸이 작고 많을 때. 서브파티션이면 거의 항상 |
처음 지을 때는 CTAS로 짓는다 | 이관 · 재구성 |
칸 단위 재적재는 스테이징 테이블을 CTAS로 짓고 EXCHANGE PARTITION으로 바꿔 넣는다 | 주간 재적재처럼 칸을 통째로 갈아 끼울 때(이 화에서는 재지 않았다) |
| 칸 수는 근거를 두고 정한다. 해시 서브파티션은 무엇을 위해 나누는지(partition-wise 조인, 경합 분산) 적어 둔다 | 설계 승인(T2 「적재 비용」 · 「파티션」 칸) |
9. 병렬 — Free에서는 계획부터 직렬이다
9.1 개념 (원천 II권 Ch7)
| 용어 | 뜻 |
|---|---|
| QC (Query Coordinator) | 병렬 문장을 받은 세션. 일을 나눠 주고 결과를 모은다 |
| PX 서버(슬레이브) | 실제로 일하는 프로세스. 생산자 집합과 소비자 집합 두 벌로 나뉜다(DOP 8이면 최대 16개) |
| Granule | 일을 나누는 단위. 블록 범위(PX BLOCK ITERATOR) 또는 파티션(PX PARTITION RANGE ALL) |
| TQ (Table Queue) | 생산자에서 소비자로 가는 통로. 실행계획의 TQ 열(Q1,00 · Q1,01)과 PX SEND / PX RECEIVE 줄로 보인다 |
| IN-OUT | P->P(서버끼리 재분배) · P->S(QC로 모음) · PCWP(같은 서버 안에서 이어서) · S->P |
| PQ_DISTRIBUTE | 조인할 때 행을 어떻게 나눠 보낼지 정한다: HASH HASH · BROADCAST NONE · NONE NONE(partition-wise) … |
| Downgrade | 요청한 DOP만큼 서버를 못 얻으면 DOP를 낮춰서(또는 직렬로) 돈다. V$SYSSTAT의 'Parallel operations downgraded …'에 기록된다 |
9.2 Free에서 실제로 본 것 (실습/08)
| 증거 | 값 |
|---|---|
힌트 없음 vs PARALLEL(o 8)의 Plan hash value | 둘 다 4189269332(같은 계획) |
실행계획의 PX … 줄 | 없다 |
Hint Report | U - Unused PARALLEL(o 8) |
V$PQ_TQSTAT · V$PX_SESSION | 0행 · 0행 |
queries parallelized · Parallel operations downgraded to serial | 0 · 0. 강등 기록조차 없다. 계획을 세울 때부터 직렬이다 |
V$SESSION.PQ_STATUS · PDML_STATUS | 처음부터 DISABLED. FORCE PARALLEL QUERY, ENABLE PARALLEL DML 뒤에도 DISABLED(에러 없음) |
INSERT /*+ APPEND PARALLEL(t 4) */ … SELECT /*+ PARALLEL(o 4) */ | LOAD AS SELECT(직접 경로는 된다) · 두 PARALLEL 모두 Unused |
parallel_max_servers · cpu_count · V$OPTION 'Parallel execution' | 1 · 2 · FALSE |
이 DB에서는 "PARALLEL 8을 붙였더니 빨라졌다"는 말도, "안 빨라졌다"는 말도 할 수 없다. 에러가 없으니 붙여 둔 사람은 효과가 있었다고 믿기 쉽다. 이 화의 판정은 '판정 불가 → 힌트를 뺀다 → 운영에서 단계별 시각과 함께 다시 잰다'였다.
9.3 판독 과제 — EE의 병렬 계획은 이렇게 생겼다
⚠️ 아래는 이 컨테이너에서는 나오지 않는 모양이다. 오라클 문서(VLDB and Partitioning Guide의 Using Parallel Execution)가 설명하는 실행계획의 형태를 우리 테이블 이름으로 옮겼다. 숫자 열(Rows · Cost · Time)은 잰 값이 아니어서 일부러 비웠다.
xplan
SELECT /*+ PARALLEL(o 4) PARALLEL(c 4) */ c.CUS_GD, SUM(o.ORD_AMT)
FROM T_ORD_BIG o JOIN M_CUS c ON c.CUS_ID = o.CUS_ID GROUP BY c.CUS_GD;
| Id | Operation | Name | TQ | IN-OUT | PQ Distrib |
| 0 | SELECT STATEMENT | | | | |
| 1 | PX COORDINATOR | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10003 | Q1,03 | P->S | QC (RAND) |
| 3 | HASH GROUP BY | | Q1,03 | PCWP | |
| 4 | PX RECEIVE | | Q1,03 | PCWP | |
| 5 | PX SEND HASH | :TQ10002 | Q1,02 | P->P | HASH |
| 6 | HASH GROUP BY | | Q1,02 | PCWP | |
| 7 | HASH JOIN | | Q1,02 | PCWP | |
| 8 | PX RECEIVE | | Q1,02 | PCWP | |
| 9 | PX SEND HASH | :TQ10000 | Q1,00 | P->P | HASH |
| 10 | PX BLOCK ITERATOR | | Q1,00 | PCWC | |
| 11 | TABLE ACCESS FULL | M_CUS | Q1,00 | PCWP | |
| 12 | PX RECEIVE | | Q1,02 | PCWP | |
| 13 | PX SEND HASH | :TQ10001 | Q1,01 | P->P | HASH |
| 14 | PX BLOCK ITERATOR | | Q1,01 | PCWC | |
| 15 | TABLE ACCESS FULL | T_ORD_BIG | Q1,01 | PCWP | |읽을 것: ① QC는 몇 번 줄인가 ② Granule은 블록 범위인가, 파티션인가 ③ TQ는 몇 개이고 각각 무엇을 보내나 ④ 지금은 두 테이블을 HASH로 재분배했다. PQ_DISTRIBUTE(o BROADCAST NONE)(조인의 두 번째 테이블 o 기준: 바깥 c는 BROADCAST, 안쪽 o는 NONE)로 바꾸면 9번과 13번 줄은 무엇이 되나 ⑤ DOP 4를 다 얻지 못하면 무슨 일이 일어나고 어디에 기록되나. 정답은 3_실습과_기록.md STEP 9에 있다.
대규모 병렬 실측(서버 수, TQ별 행 분포 V$PQ_TQSTAT, 다운그레이드)은 별권에서 EE로 한다.
10. P1 — rev00 → rev01 → rev02
10.1 측정 (실습/05 · 10만 건 · 각 판 TRUNCATE부터 · 워밍업 1회 뒤)
| 판 | 경과 | redo | undo | 커밋 | 논리 읽기 | 블록 변경 |
|---|---|---|---|---|---|---|
| rev00 커서 FOR 루프 · 1,000건 커밋 | 0.89초 | 28.66MB | 6.50MB | 101 | 110,444 | 204,942 |
rev01 INSERT … SELECT · 커밋 한 번 | 0.08초 | 4.47MB | 0.26MB | 2 | 7,408 | 4,791 |
rev02 + APPEND | 0.11초 | 0.08MB | 0.02MB | 2 | 2,858 | 827 |
2회차: rev00 0.89초 · 28.66MB · 6.50MB / rev01 0.23초 · 4.47MB · 0.26MB / rev02 0.11초 · 0.08MB · 0.02MB. redo와 undo는 같고 초만 흔들린다(rev01 0.08 ↔ 0.23).
- 커밋 수에는
TRUNCATE의 커밋 1이 들어 있다(13화 §7과 같다). - rev01의 redo가 rev00의 1/6인 까닭: 한 문장 INSERT는 여러 행을 한 블록에 한꺼번에 넣고, 그 묶음 단위로 redo와 undo를 적는다. 루프는 행마다 따로 적는다.
- rev02의 undo 0.02MB: 직접 경로는 고수위선 위의 새 블록에 쓰므로, 롤백은 '고수위선을 안 올리는 것'으로 끝난다. 데이터를 되돌릴 정보를 적지 않는다(← 13화 정정 전 사본함). 2020.07에 강도현이 1,000건씩 끊은 근거(ORA-30036)가 이 판에서는 사라진다.
10.2 13화 「커밋 주기 기준」과의 관계
| 결정표 | P1 |
|---|---|
| #1 집합 연산 한 문장으로 쓸 수 있나? → 그렇게 쓴다. 커밋은 끝에 한 번. 언두가 걱정이면 14화 | 쓸 수 있다 → rev01. 언두 걱정 → rev02(undo 0.02MB) |
| #2 fetch across commit인가? | 아니다(13화 판정 그대로). 이제 루프 자체가 없다 |
두 기준은 충돌하지 않는다. 13화 결정표의 1번 줄에서 멈추는 경우이고, 14화는 그 줄의 "언두가 걱정이면"에 숫자를 붙였다. 대가는 §4의 배타 락이다. P1이 마감 전에 끝난다는 전제에서만 안전하다.
10.3 남의 눈 — 적재 중에 보이는 것 (실습/05 [남의 눈])
| 판 | 5만 번째 행을 넣는 순간 다른 트랜잭션이 세는 정산 |
|---|---|
| rev00(1,000건마다 커밋) | 50,000(반쯤 적재된 상태가 보인다. 09:03 조민우) |
| rev01 · rev02(한 문장) | 중간이 없다. TRUNCATE 뒤 커밋까지 0건, 커밋하는 순간 100,000 |
10.4 정합성 회귀 (체크리스트 A · B · E · H)
| # | 항목 | 결과 |
|---|---|---|
| A | 기준 | rev00 결과(강도현 원본) |
| B | 건수 · 합계 · 양방향 MINUS · 번호 중복 | 100,000 = 100,000 · 25,519,833,100 = 25,519,833,100 · 0 / 0 · 0 → 통과 |
| E | 경계: 번호(STLM_SEQ)는 ORDER BY 없이 읽은 순서로 붙는다 | 지금 T_ORD는 ORD_SEQ 순으로 쌓여 있어 차이가 0이다. 원천이 다시 쌓이면(MOVE, 재적재) 바뀔 수 있다. rev00도 같다 |
| H | 영향: 정산 테이블을 읽는 화면 | 적재하는 동안 0건 → 커밋 순간 전부. 반쯤 적재된 상태는 사라진다. 마감을 넘기면 배타 락으로 쓰기가 막힌다 |
11. 월말 정리 — DELETE · SHRINK · DROP PARTITION
T_ORD_LOG(05화 생성식 · 50만 행 · 900일 · 주문번호 인덱스 1개) · 한 달 = 2023년 1월 17,235행(실습/06)
| 정리 방법 | redo | undo | 블록 변경 | 경과 | 그 뒤 전체 스캔 |
|---|---|---|---|---|---|
DELETE | 4.84MB | 2.32MB | 36,691 | 0.06초 | 3,602 → 3,602 |
(매달 청소) SHRINK SPACE | 17.96MB | 6.62MB | 126,577 | 2.21초 | 3,489 |
DROP PARTITION | 0.01MB | 0.01MB | 96 | 0.04초 | 3,630 → 3,505 |
DROP PARTITION … UPDATE GLOBAL INDEXES (다음 달) | 0.01MB | 0.01MB | 99 | 0.03초 | 인덱스 VALID |
(비교) 다음 달을 DELETE | 4.45MB | 2.11MB | 33,870 | 0.05초 | — |
2회차:
DELETE4.84MB ·SHRINK18.00MB → 전체 스캔 3,492 ·DROP PARTITION0.01MB(블록 변경 97). redo는 같고SHRINK뒤 블록 수만 몇 개 다르다.
- 05화의 숫자(3,602)가 같은 생성식에서 그대로 나온다.
DELETE는 분필 선을 내리지 않는다. SHRINK는 지운 한 달보다 3.7배 비싸다. 남은 행을 전부 옮겨 쓰기 때문이다. 게다가 매달 해야 한다.- 파티션 30칸(+ 빈 PMAX)으로 지은 이력 테이블은 세그먼트 30개 · 240MB를 차지한다(파티션 없는 쪽은 29MB). §8.5에서 본 8MB 첫 익스텐트 때문이다.
CTAS로 지었으므로 빈 블록은 없다(전체 스캔 3,630 ≈ 3,602). - 연습 DB에서 한 달
DELETE는 0.06초다. 월말 정리의 청구서는 시간이 아니라 '읽는 양'과 '매달의 청소'로 온다.
12. 이 화의 측정값 전부
체인 단계 분해 (연습 DB · 단독 실행)
| 단계 | 지금 (운영 그대로) | 바꾼 뒤 |
|---|---|---|
| ① 재적재 100만 · 인덱스 3개 | 일반 INSERT 329초(2회차 542초) · redo 916.6MB | APPEND + 끄고 켜기 5.0초 · redo 0.6MB |
| ① 통계 수집 | 1.06초 | 1.06초(유지: 히스토그램) |
| ② 월말 이력 정리 | DELETE 0.06초 · 전체 스캔 3,602 그대로 | DROP PARTITION 0.04초(파티션 승인 시) |
③ 월마감 집계 PARALLEL(8) (대역: 고객별 합계) | 0.05초 · 힌트 Unused | 힌트 제거 · 운영 재측정 |
| ④ P1 | rev00 0.89초 · 28.66MB | rev02 0.11초 · 0.08MB |
측정 환경
Oracle AI Database 26ai Free Release 23.26.2.0.0 · cpu_count 2 · db_block_size 8192 · NOARCHIVELOG · FORCE_LOGGING NO · 온라인 리두 로그 10MB × 2 · log_buffer 약 4MB · parallel_max_servers 1 · V$OPTION Partitioning TRUE · Parallel execution FALSE · skip_unusable_indexes TRUE · deferred_segment_creation TRUE. 버거원: 같은 버전 · dev 스키마(L2 카드 §4 적재 · 통계 100%).
13. Free에서 못 잰 것
| 못 잰 것 | 왜 | 대신 한 것 |
|---|---|---|
| 병렬 실행(PX 서버 · TQ 분배 · 다운그레이드) | Free는 병렬을 하지 않는다. 계획부터 직렬이다(§9.2) | '안 됐다'는 증거 다섯 가지 실측 + PX 계획 판독 과제(§9.3) · 대규모 병렬은 별권 |
ARCHIVELOG에서의 직접 경로 redo · NOLOGGING 효과 | 로그 모드를 바꾸려면 인스턴스를 재기동해야 하고, 이 교재의 공용 컨테이너 상태가 바뀐다 | NOARCHIVELOG에서 [C] ≈ [D]를 실측하고, 운영과의 차이는 §5 표로 정리했다 |
| 다중 세션의 오른쪽 끝 블록 경합 크기 | CPU 2개. 12화에서 '이벤트가 생긴다'까지만 쟀다 | 한 세션의 분할 종류 · redo · 범위 검색 상실(§2.4) |
| 운영 체인의 단계별 소요 | 운영 로그에 단계별 시각이 없다 | 연습 DB에서 단계별로 재현했다(T3 재발방지 1번) |
| 데이터 증가에 따른 추세선 | 위와 같다 | T3 「추세」 칸은 단계별 시각을 수집한 뒤에 채운다 |
EXCHANGE PARTITION 재적재 | 이 화의 범위를 넘는다 | §8.5 처방 표에 '재지 않았다'고 표기 |
14. 원천 좌표
전체 지도 →
00_자료준비/06_원서와_공식문서_지도.mdPDF 페이지 = 책 페이지 − 2 ·성능고도화II_목차색인.md(공개 사이트에는 없음)
성능고도화 II권 (주 원천)
| 주제 | 책 페이지 |
|---|---|
| Ch6 테이블 파티셔닝: 기본 구조 · Range · 해시 · 리스트 · 결합 | 624 · 625 · 629 · 630 · 632 · 633 |
| 파티션 Pruning: 기본 · 서브쿼리 · 조인 필터 · SQL 조건절 작성 시 주의사항 | 638 · 645 · 648 · 654 |
| 인덱스 파티셔닝: 로컬 · 비파티션 · 글로벌 · Prefixed vs Nonprefixed · 구성 예시 · 글로벌의 효용성 | 657 · 659 · 660 · 660 · 662 · 663 · 668 |
| 파티셔닝 선택 기준 | 673 |
| Ch7 병렬 처리: 기본 개념 · 테이블 큐 · IN-OUT · 데이터 재분배 · Granule · 대기이벤트 | 682 · 685 · 688 · 690 · 692 · 694 |
| 병렬 Order By · Group By · 병렬 조인 | 698 · 700 · 706 |
(Ch2) 조인을 내포한 DML · MERGE · 다중 테이블 INSERT | 288 · 295 · 300 |
| (I권) Direct Path I/O (Read · Write) | 457 · 459 · 460 |
오라클 공식문서
| 주제 | 매뉴얼 | 장 제목 |
|---|---|---|
직접 경로 INSERT · APPEND · 제약 조건 | admin | Managing Tables: Loading Tables (Direct-Path INSERT) |
INSERT 힌트 · NOLOGGING | sqlrf | INSERT · Comments (Hints) |
파티셔닝 · Pruning · 파티션 인덱스 · 파티션 유지보수(UPDATE INDEXES) | vldbg | Partitioning Concepts · Partitioning for Availability, Manageability, and Performance · Maintenance of Partitions |
| 병렬 실행 (QC · Granule · TQ · Downgrade) | vldbg | Using Parallel Execution |
| 온라인 통계 (대량 적재) | tgsql | Optimizer Statistics Concepts: Online Statistics Gathering for Bulk Loads |
V$PQ_TQSTAT · V$PX_SESSION · V$LOCK · DBA_INDEX_USAGE | refrn | Dynamic Performance Views · Static Data Dictionary Views |
| 에디션별 기능(파티셔닝 = EE 유상 옵션 · Free의 제한) | dblic | Licensing Information |
이 화에서 꼭 확인할 뷰 · 통계
sql
V$MYSTAT / V$STATNAME -- redo size · undo change vector size · db block changes · session logical reads
-- leaf node splits · leaf node 90-10 splits
V$SESSION_EVENT -- log file switch (checkpoint incomplete) · log file switch completion
V$LOCK -- TM 의 LMODE (3 vs 6)
DBMS_SPACE.SPACE_USAGE -- full · fs1~fs4 · unformatted (파티션 · 서브파티션 단위로)
USER_SEGMENTS -- 세그먼트 수 · bytes · extents · initial_extent
USER_TAB_COL_STATISTICS -- histogram · notes (STATS_ON_LOAD)
DISPLAY_CURSOR(FORMAT => 'ALLSTATS LAST +PARTITION +HINT_REPORT +NOTE')다음 화가 여기서 가져가는 것
| 화 | 가져가는 것 |
|---|---|
| 15 | 커서 FOR 루프는 왜 느린가: rev00 0.89초와 rev01 0.08초의 차이가 어디서 나는지(SQL ↔ PL/SQL 왕복) · 한 문장으로 못 바꾸는 행 단위 로직 · T3 2호 |
| 16 · 17 | 운영 체인을 시간으로 본다. 단계별 시각이 없어서 연습 DB에서 재현해야 했던 부분(AWR · ASH) |
| 18 | 「SQL 코딩 가이드」 후보: 파티션 키는 가공하지 않는다 · 적재 방식(일반/APPEND/끄고 켜기)을 배치 머리에 적는다 · APPEND를 붙였으면 LOAD AS SELECT를 확인한다 · T3 · T2 개정 |