편집 요약 없음 |
(→기본 설명) |
||
| 6번째 줄: | 6번째 줄: | ||
---- | ---- | ||
== 기본 설명 == | == 기본 설명 == | ||
{{{본문| | {{{본문| | ||
값의 의미 === | |||
# 범위 | |||
- **최소값**: 테이블의 블록 수 (완벽하게 정렬된 경우) | |||
- **최대값**: 테이블의 행 수 (완전히 무작위로 분산된 경우) | |||
### 해석 방법 | |||
```sql | |||
-- Clustering Factor 확인 | |||
SELECT | |||
index_name, | |||
table_name, | |||
clustering_factor, | |||
num_rows, | |||
leaf_blocks, | |||
-- 테이블 블록 수 | |||
(SELECT blocks FROM dba_tables t | |||
WHERE t.owner = i.table_owner | |||
AND t.table_name = i.table_name) AS table_blocks | |||
FROM dba_indexes i | |||
WHERE table_owner = 'HR' | |||
AND table_name = 'EMPLOYEES'; | |||
``` | |||
**판단 기준:** | |||
| Clustering Factor | 상태 {{!}} 의미 {{!}} | |||
|------------------|------|------| | |||
| ≈ 테이블 블록 수 | **좋음** ✅ {{!}} 데이터가 인덱스 순서대로 잘 정렬됨 {{!}} | |||
| 테이블 블록 수 ~ 행 수 사이 | 보통 {{!}} 부분적으로 정렬됨 {{!}} | |||
| ≈ 테이블 행 수 | **나쁨** ❌ {{!}} 데이터가 무작위로 흩어져 있음 {{!}} | |||
--- | |||
## Clustering Factor의 계산 원리 | |||
``` | |||
인덱스를 순차적으로 스캔하면서: | |||
- 이전 행과 다른 블록에 있으면: CF++ | |||
- 이전 행과 같은 블록에 있으면: CF 증가 없음 | |||
``` | |||
### 예제로 이해하기 | |||
**시나리오 1: 좋은 Clustering Factor** | |||
``` | |||
인덱스 순서: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 | |||
블록 배치: [1,2,3] [4,5,6] [7,8,9] [10] | |||
Block1 Block2 Block3 Block4 | |||
Clustering Factor = 4 (블록 수) | |||
``` | |||
**시나리오 2: 나쁜 Clustering Factor** | |||
``` | |||
인덱스 순서: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 | |||
블록 배치: [1] [5] [2] [7] [3] [9] [4] [10] [6] [8] | |||
B1 B2 B3 B4 B5 B6 B7 B8 B9 B10 | |||
Clustering Factor = 10 (행 수) | |||
``` | |||
--- | |||
## 성능에 미치는 영향 | |||
### 좋은 Clustering Factor (낮은 값) | |||
```sql | |||
-- 예: CF = 1,000 (블록 수와 유사) | |||
SELECT * FROM employees | |||
WHERE department_id BETWEEN 10 AND 50; | |||
-- 결과: 약 1,000개 블록만 읽음 | |||
-- 각 블록에서 여러 행을 읽을 수 있음 | |||
``` | |||
**장점:** | |||
- 인덱스 Range Scan이 효율적 | |||
- 물리적 I/O 최소화 | |||
- Buffer Cache 효율 증가 | |||
### 나쁜 Clustering Factor (높은 값) | |||
```sql | |||
-- 예: CF = 100,000 (행 수와 유사) | |||
SELECT * FROM employees | |||
WHERE department_id BETWEEN 10 AND 50; | |||
-- 결과: 약 50,000개 블록 읽음 | |||
-- 각 행마다 다른 블록을 읽어야 함 | |||
``` | |||
**단점:** | |||
- 많은 물리적 I/O 발생 | |||
- 동일 블록을 반복 읽기 | |||
- Full Table Scan이 더 효율적일 수 있음 | |||
--- | |||
## 실무 조회 쿼리 | |||
### 1. Clustering Factor 상태 확인 | |||
```sql | |||
SELECT | |||
i.owner, | |||
i.table_name, | |||
i.index_name, | |||
i.clustering_factor, | |||
t.num_rows, | |||
t.blocks AS table_blocks, | |||
-- CF 품질 지표 | |||
ROUND(i.clustering_factor / NULLIF(t.blocks, 0), 2) AS cf_ratio, | |||
CASE | |||
WHEN i.clustering_factor <= t.blocks * 1.2 THEN '좋음 ✅' | |||
WHEN i.clustering_factor <= t.blocks * 5 THEN '보통 ⚠️' | |||
WHEN i.clustering_factor <= t.num_rows * 0.5 THEN '나쁨 ❌' | |||
ELSE '매우 나쁨 🚫' | |||
END AS cf_status, | |||
-- 효율성 점수 (낮을수록 좋음) | |||
ROUND((i.clustering_factor / NULLIF(t.num_rows, 0)) * 100, 2) AS efficiency_pct | |||
FROM dba_indexes i | |||
JOIN dba_tables t ON i.table_owner = t.owner | |||
AND i.table_name = t.table_name | |||
WHERE i.owner = 'HR' | |||
AND t.num_rows > 0 | |||
ORDER BY efficiency_pct DESC; | |||
``` | |||
### 2. 문제가 있는 인덱스 찾기 | |||
```sql | |||
SELECT | |||
i.owner, | |||
i.table_name, | |||
i.index_name, | |||
i.clustering_factor, | |||
t.blocks AS table_blocks, | |||
t.num_rows, | |||
ROUND(i.clustering_factor / t.num_rows * 100, 2) AS cf_pct, | |||
ROUND((t.num_rows * t.avg_row_len) / 1024 / 1024, 2) AS table_size_mb | |||
FROM dba_indexes i | |||
JOIN dba_tables t ON i.table_owner = t.owner | |||
AND i.table_name = t.table_name | |||
WHERE i.owner NOT IN ('SYS', 'SYSTEM') | |||
AND t.num_rows > 10000 -- 작은 테이블 제외 | |||
AND i.clustering_factor > t.blocks * 10 -- CF가 블록 수의 10배 이상 | |||
ORDER BY (i.clustering_factor / t.num_rows) DESC; | |||
``` | |||
### 3. 인덱스 효율성 분석 | |||
```sql | |||
WITH index_stats AS ( | |||
SELECT | |||
i.owner, | |||
i.table_name, | |||
i.index_name, | |||
i.clustering_factor, | |||
t.blocks, | |||
t.num_rows, | |||
-- 인덱스로 100행을 읽을 때 예상 블록 접근 수 | |||
CASE | |||
WHEN t.num_rows > 0 THEN | |||
ROUND((i.clustering_factor / t.num_rows) * 100, 0) | |||
ELSE 0 | |||
END AS blocks_per_100_rows | |||
FROM dba_indexes i | |||
JOIN dba_tables t ON i.table_owner = t.owner | |||
AND i.table_name = t.table_name | |||
WHERE i.owner = 'HR' | |||
) | |||
SELECT | |||
owner, | |||
table_name, | |||
index_name, | |||
clustering_factor, | |||
blocks AS table_blocks, | |||
num_rows, | |||
blocks_per_100_rows, | |||
CASE | |||
WHEN blocks_per_100_rows <= 10 THEN 'Range Scan 효율적' | |||
WHEN blocks_per_100_rows <= 50 THEN 'Range Scan 사용 가능' | |||
ELSE 'Full Scan 고려' | |||
END AS recommendation | |||
FROM index_stats | |||
ORDER BY blocks_per_100_rows DESC; | |||
``` | |||
--- | |||
## Clustering Factor 개선 방법 | |||
### 방법 1: 테이블 재구성 (MOVE) | |||
```sql | |||
-- 인덱스 컬럼 순서대로 테이블 재정렬 | |||
ALTER TABLE hr.employees MOVE; | |||
-- 인덱스 재구성 | |||
ALTER INDEX hr.emp_dept_idx REBUILD; | |||
-- 통계 재수집 | |||
EXEC DBMS_STATS.GATHER_TABLE_STATS('HR', 'EMPLOYEES'); | |||
-- CF 확인 | |||
SELECT index_name, clustering_factor | |||
FROM user_indexes | |||
WHERE index_name = 'EMP_DEPT_IDX'; | |||
``` | |||
### 방법 2: 테이블을 인덱스 순서로 재생성 | |||
```sql | |||
-- 임시 테이블 생성 (인덱스 순서대로) | |||
CREATE TABLE employees_new AS | |||
SELECT * FROM employees | |||
ORDER BY department_id, employee_id; | |||
-- 원본 테이블 교체 | |||
DROP TABLE employees; | |||
RENAME employees_new TO employees; | |||
-- 인덱스 재생성 | |||
CREATE INDEX emp_dept_idx ON employees(department_id); | |||
-- 통계 수집 | |||
EXEC DBMS_STATS.GATHER_TABLE_STATS('HR', 'EMPLOYEES'); | |||
``` | |||
### 방법 3: Partitioning 사용 | |||
```sql | |||
-- 파티션 테이블로 변환 | |||
ALTER TABLE employees MODIFY | |||
PARTITION BY RANGE (department_id) | |||
( | |||
PARTITION p1 VALUES LESS THAN (20), | |||
PARTITION p2 VALUES LESS THAN (40), | |||
PARTITION p3 VALUES LESS THAN (60), | |||
PARTITION p4 VALUES LESS THAN (MAXVALUE) | |||
); | |||
-- 로컬 인덱스 생성 | |||
CREATE INDEX emp_dept_idx ON employees(department_id) LOCAL; | |||
``` | |||
### 방법 4: IOT (Index-Organized Table) 사용 | |||
```sql | |||
-- 기존 테이블을 IOT로 변환 | |||
CREATE TABLE employees_iot | |||
ORGANIZATION INDEX | |||
INCLUDING employee_id | |||
OVERFLOW | |||
AS SELECT * FROM employees; | |||
``` | |||
--- | |||
## Optimizer가 Clustering Factor를 사용하는 방법 | |||
```sql | |||
-- 실행 계획에서 Clustering Factor의 영향 확인 | |||
EXPLAIN PLAN FOR | |||
SELECT * FROM employees | |||
WHERE department_id BETWEEN 10 AND 50; | |||
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(null, null, 'BASIC +COST')); | |||
-- Cost 계산에 Clustering Factor가 사용됨 | |||
-- Cost = (Clustering Factor / Table Blocks) * Index Selectivity | |||
``` | |||
**Optimizer의 판단:** | |||
- **CF가 낮으면**: Index Range Scan 선호 | |||
- **CF가 높으면**: Full Table Scan 고려 | |||
--- | |||
## 실전 모니터링 스크립트 | |||
```sql | |||
-- 일일 CF 모니터링 | |||
CREATE OR REPLACE VIEW v_clustering_factor_monitor AS | |||
SELECT | |||
i.owner, | |||
i.table_name, | |||
i.index_name, | |||
i.clustering_factor, | |||
t.blocks, | |||
t.num_rows, | |||
ROUND(i.clustering_factor / NULLIF(t.blocks, 0), 2) AS cf_block_ratio, | |||
ROUND(i.clustering_factor / NULLIF(t.num_rows, 0) * 100, 2) AS cf_row_pct, | |||
i.last_analyzed, | |||
CASE | |||
WHEN i.clustering_factor <= t.blocks * 2 THEN 'GOOD' | |||
WHEN i.clustering_factor <= t.blocks * 10 THEN 'FAIR' | |||
ELSE 'POOR' | |||
END AS status | |||
FROM dba_indexes i | |||
JOIN dba_tables t ON i.table_owner = t.owner | |||
AND i.table_name = t.table_name | |||
WHERE i.owner NOT IN ('SYS', 'SYSTEM', 'XDB', 'OUTLN') | |||
AND t.num_rows > 1000; | |||
-- 사용 | |||
SELECT * FROM v_clustering_factor_monitor | |||
WHERE status = 'POOR' | |||
ORDER BY cf_row_pct DESC; | |||
``` | |||
--- | |||
## 주의사항 | |||
1. **통계 정보 최신화** | |||
```sql | |||
-- CF는 통계 수집 시 계산됨 | |||
EXEC DBMS_STATS.GATHER_INDEX_STATS('HR', 'EMP_DEPT_IDX'); | |||
``` | |||
2. **DML 작업 후 CF 변화** | |||
- INSERT/UPDATE/DELETE 후 CF가 나빠질 수 있음 | |||
- 주기적인 통계 수집 필요 | |||
3. **파티션 테이블** | |||
- 파티션별로 CF가 다를 수 있음 | |||
- USER_IND_PARTITIONS에서 확인 | |||
4. **Multiblock Read** | |||
- CF가 나빠도 db_file_multiblock_read_count가 크면 영향 감소 | |||
--- | |||
## 요약 | |||
| 상황 | Clustering Factor {{!}} 권장 사항 {{!}} | |||
|-----|------------------|---------| | |||
| OLTP, 자주 Range Scan | 테이블 블록 수와 유사 {{!}} 테이블 재구성 고려 {{!}} | |||
| Batch, Full Scan 위주 | 높아도 무관 {{!}} 현상 유지 {{!}} | |||
| 자주 변경되는 테이블 | 주기적으로 나빠짐 {{!}} 파티셔닝 검토 {{!}} | |||
| 대용량 테이블 | 매우 높음 {{!}} IOT 또는 파티셔닝 {{!}} | |||
Clustering Factor는 인덱스 성능을 판단하는 핵심 지표이므로, DBA는 정기적으로 모니터링하고 필요시 개선 작업을 수행해야 합니다! | |||
}}} | |||
== 예제 == | == 예제 == | ||
2025년 10월 2일 (목) 18:42 판
문서 제목 없음
menu_book {{{내용}}}
기본 설명
값의 의미 ===
- 범위
- **최소값**: 테이블의 블록 수 (완벽하게 정렬된 경우) - **최대값**: 테이블의 행 수 (완전히 무작위로 분산된 경우)
- 해석 방법
```sql -- Clustering Factor 확인 SELECT
index_name,
table_name,
clustering_factor,
num_rows,
leaf_blocks,
-- 테이블 블록 수
(SELECT blocks FROM dba_tables t
WHERE t.owner = i.table_owner
AND t.table_name = i.table_name) AS table_blocks
FROM dba_indexes i WHERE table_owner = 'HR'
AND table_name = 'EMPLOYEES';
```
- 판단 기준:**
예제
{{{예제}}}
이전글/다음글
- [[]]
- [[]]
관련 문서
- [[]]
- [[]]
FAQ
- 질문
- 답변
- 질문
- 답변