테이블 랜덤 액세스의 원리: ROWID·Buffer I/O·후행 조건
Index가 찾은 ROWID와 Table Block 방문 비용을 연결해 Random Access 최소화 원리를 이해합니다.
핵심 요약
B-tree Index는 조건에 맞는 Index Key와 ROWID 후보를 찾습니다. Query에 필요한 조건이나 반환 Column이 Index에 없으면 Oracle은 ROWID로 Heap Table Block을 찾아가 실제 Row를 읽습니다.
Index Leaf Scan
→ 후보 ROWID 생성
→ Table Block 방문
→ Table Filter·Projection
→ 최종 Row 반환
Table Random Access 비용은 세 단계로 분리해 봅니다.
1. Index 작업
→ 몇 Leaf Block·Entry를 읽었는가
2. 후보 ROWID
→ Table Operation으로 몇 Row를 전달했는가
3. Table 작업
→ 몇 Table Block을 방문하고 몇 Row를 버렸는가
최종 결과가 50행이어도 다음 두 실행은 전혀 다릅니다.
실행 A
Index A-Rows 60
Index Buffers 10
Table Buffers 30
최종 Row 50
실행 B
Index A-Rows 10,000
Index Buffers 120
Table Buffers 8,100
최종 Row 50
실행 B의 핵심 낭비는 10,000개의 후보 ROWID를 Table 단계까지 가져가 50개만 남긴 것입니다.
이 이론의 범위
이 이론은 SQLP의
SQL 고급활용 및 튜닝 → 인덱스 튜닝 → 테이블 액세스 최소화범위에서 ROWID, Table Random Access, Index Filter, Covering과 대안 Access Path를 다룹니다.
학습 목표
이 이론을 학습한 뒤에는 다음을 설명할 수 있어야 합니다.
- Index Scan과
TABLE ACCESS BY INDEX ROWID의 역할을 구분한다. - Extended Physical ROWID의 구성과 변경 가능성을 설명한다.
- 후보 ROWID·Table Block·최종 Row의 차이를 설명한다.
TABLE ACCESS BY INDEX ROWID BATCHED의 목적과 한계를 설명한다.- Clustering Factor가 Table Block 방문량에 미치는 영향을 설명한다.
- Row Migration·Chaining이 ROWID Access 비용을 늘릴 수 있음을 설명한다.
- Index Column 추가의 Range 축소·Index Filter·Covering 효과를 구분한다.
- Covering·Index Join·Full Scan·Partition Pruning 대안을 비교한다.
- Starts·A-Rows·Buffers·Reads와
LAST_*를 이용해 실제 비용을 검증한다. - 조회 이득과 신규 Index의 DML·Redo·공간 비용을 함께 판단한다.
1. Index Scan과 Table Access는 서로 다른 Row Source다
다음 Index가 있습니다.
CREATE INDEX orders_status_ix
ON orders(status);
Query입니다.
SELECT order_id,
customer_id,
amount
FROM orders
WHERE status = 'READY'
AND region = 'SEOUL';
ORDERS_STATUS_IX에는 region, order_id, customer_id, amount가 없습니다.
일반적인 처리 흐름입니다.
INDEX RANGE SCAN ORDERS_STATUS_IX
→ status='READY' Entry와 ROWID 찾기
TABLE ACCESS BY INDEX ROWID BATCHED ORDERS
→ ROWID가 가리키는 Row 읽기
→ region='SEOUL' Filter
→ SELECT Column 반환
INDEX RANGE SCAN
= 후보 위치 탐색
TABLE ACCESS BY INDEX ROWID
= 실제 Heap Table Row 읽기
Index가 사용됐다는 사실만으로 Table Access가 적다고 판단하지 않습니다. Index가 모든 필요한 Column을 제공하면 Table Access가 생략될 수도 있습니다.
2. Physical ROWID의 구성과 의미
Heap-Organized Table의 Extended ROWID는 개념적으로 다음 네 요소를 포함합니다.
Data Object Number
+ Tablespace-Relative File Number
+ Data Block Number
+ Row Number
표시 형식의 개념입니다.
OOOOOO FFF BBBBBB RRR
- Data Object: Row가 속한 Segment
- Relative File: Row가 있는 Data File
- Block: Row가 저장된 Data Block
- Row Number: Block Row Directory의 Entry
ROWID는 Row 위치에 빠르게 접근할 수 있게 하지만 “접근 비용이 0”이라는 뜻은 아닙니다.
ROWID 획득
→ Buffer Cache에서 Block 탐색
→ Block Pin·Consistent Read
→ Row Directory·Row Piece 확인
→ Predicate·Projection 처리
Cache Hit이면 Physical Read가 없을 수 있지만 Logical I/O와 CPU는 남습니다. Cache Miss이면 Physical Read와 I/O Wait가 추가됩니다.
2.1 ROWID는 영구 업무 Key가 아니다
ROWID는 다음 상황에서 바뀔 수 있습니다.
- Row Movement가 허용된 Partition Key Update
SHRINK SPACE- Flashback Table
- Export·Import
- 일부 Table Move·재구성
업무 Identifier로 ROWID를 장기간 저장하지 않고 PK·UK를 사용합니다.
3. 후보 ROWID와 최종 결과를 비교한다
실측 예시입니다.
| Id | Operation | Starts | A-Rows | Buffers |
|---:|-------------------------------------|-------:|-------:|--------:|
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED | 1 | 50 | 8,100 |
|* 2 | INDEX RANGE SCAN ORDERS_STATUS_IX | 1 | 10,000 | 120 |
Predicate입니다.
1 - filter(region='SEOUL')
2 - access(status='READY')
해석합니다.
Index 후보 10,000
Table 최종 50
Table 탈락 9,950
확인할 질문입니다.
- Index가 만든 후보 ROWID는 몇 개인가
- Table에서 어떤 조건이 늦게 평가되는가
- Table Buffers가 왜 큰가
- Clustering이 나쁜가
- 후보 수를 Index에서 줄일 수 있는가
- 결과 비율이 커서 Full Scan이 더 적절한가
Starts=1은 Row Source가 부모에 의해 한 번 시작됐다는 뜻이지 ROWID 접근이 한 번이라는 뜻이 아닙니다.
4. Index 작업과 Table 작업을 따로 진단한다
4.1 Leaf Scan 비효율
INDEX RANGE SCAN
A-Rows 50
Buffers 20,000
TABLE ACCESS
A-Rows 50
추가 Buffers 30
Index Filter에서 많은 Entry를 제거했을 수 있습니다.
우선 확인합니다.
- 중간 Index Column 누락
- 첫 Range가 너무 넓음
- 후행 Predicate가 Filter
- Skip Scan·INLIST·Nested Loops Starts
- Column 순서
4.2 후보 ROWID 과다
INDEX RANGE SCAN
A-Rows 100,000
Buffers 200
TABLE ACCESS
A-Rows 1,000
Buffers 80,000
Index 자체는 적은 Buffer로 많은 후보를 만들었지만, Table에서 대부분 제거했습니다.
개선 후보입니다.
- Table Filter Column을 Index에 추가
- Equality Column을 첫 Range 앞에 배치
- 선택적인 복합 Index
- 결과 비율이 크면 Full·Partition Scan
4.3 Table Block 분산
같은 10,000 후보 ROWID라도 비용은 다릅니다.
Plan A
10,000 ROWID
Table Buffers 300
Plan B
10,000 ROWID
Table Buffers 9,000
Plan B는 ROWID가 더 많은 Block에 분산됐을 가능성이 큽니다.
5. TABLE ACCESS BY INDEX ROWID BATCHED
Oracle의 BATCHED Access는 Index에서 몇 개의 ROWID를 모은 뒤 Table Row를 Block 순서에 가깝게 접근하려고 시도합니다.
Index에서 ROWID Batch 수집
→ Block 위치 고려
→ 같은 Block Row를 묶어 접근
목표입니다.
- 같은 Block 반복 방문 감소
- Row가 어느 정도 흩어진 경우 Clustering 보완
- Buffer Access 순서 개선
BATCHED가 의미하지 않는 것:
BATCHED
≠ Table Access 제거
≠ 후보 ROWID 감소
≠ 한 번의 Multiblock Read 보장
≠ Clustering 문제 완전 해결
후보가 100,000개이고 최종 1,000행이라면 BATCHED보다 후보 ROWID를 줄이는 설계가 우선입니다.
6. Clustering Factor
Clustering Factor는 특정 B-tree Index의 Key 순서와 Table Block 배치가 얼마나 비슷한지를 나타냅니다.
Clustering Factor가 Table Block 수에 가까움
→ 인접 Index Entry가 같은·인접 Block
→ Range Scan Table Access에 유리
Clustering Factor가 Table Row 수에 가까움
→ ROWID가 넓은 Block에 분산
→ Random Table Access 증가 가능
확인 SQL입니다.
SELECT index_name,
leaf_blocks,
clustering_factor
FROM user_indexes
WHERE table_name = 'ORDERS';
Clustering Factor는 Table 속성이 아니라 각 Index별 속성입니다. 한 Index의 Clustering을 개선하는 Table 재배치는 다른 Index의 Clustering을 악화시킬 수 있습니다.
6.1 Clustering Factor를 절대값 하나로 판단하지 않는다
함께 확인합니다.
- Table
NUM_ROWS,BLOCKS - Index
LEAF_BLOCKS - 실제 후보 ROWID
- Table Buffers·Reads
- Buffer Cache와 동시성
- Range 크기와 Bind 편중
7. Row Migration·Chaining과 추가 Block Access
Row가 한 Block에 모두 들어가지 않거나 Update로 기존 Block 공간을 초과하면 Row Piece가 여러 Block에 저장될 수 있습니다.
원래 ROWID가 가리키는 Block
→ Forwarding·추가 Row Piece
→ 다른 Block Access
ROWID가 하나여도 실제 Row를 완성하기 위해 추가 Block을 읽을 수 있습니다.
- Row Migration: Update 후 Row가 다른 Block으로 이동
- Row Chaining: Row가 너무 커 여러 Block에 걸쳐 저장
확인 후보입니다.
- 넓은 Row·긴 Variable Column
- Update가 많은 Table
CHAIN_CNT와 Segment 상태- Table Buffers가 후보 ROWID 수보다 비정상적으로 큼
무조건 Table Move를 수행하지 않고 실제 영향과 가용성·Index 재구축 비용을 검증합니다.
8. Index Column 추가의 세 가지 효과
8.1 Access Range 축소
CREATE INDEX orders_x1
ON orders(status, region);
WHERE status='READY'
AND region='SEOUL'
두 Column이 Equality Access에 사용되면 범위가 좁아집니다.
Leaf Scan 감소
→ 후보 ROWID 감소
→ Table Access 감소
8.2 Index Filter로 후보 감소
CREATE INDEX orders_x2
ON orders(status, order_date, region);
WHERE status='READY'
AND order_date>=:from_dt
AND order_date< :to_dt
AND region='SEOUL'
order_date가 첫 Range라면 후행 region이 전체 Leaf 범위를 크게 줄이지 못할 수 있습니다.
Leaf Range 유지
→ Index에서 region Filter
→ Table 전달 ROWID 감소
Index Buffers와 Table Buffers를 별도로 봅니다.
8.3 Covering으로 Table Access 제거
CREATE INDEX orders_x3
ON orders(status, region, order_id, customer_id, amount);
조건과 반환 Column을 모두 Index에서 제공하면 Table Access를 제거할 수 있습니다.
INDEX RANGE SCAN
→ 결과 완성
Covering은 Query별 상태이지 별도 Index Type이 아닙니다.
9. Covering의 이득과 비용
이득
- Table ROWID Access 제거
- Table Buffer·Physical Read 감소
- Clustering 영향 제거
- Top-N·정렬과 결합 가능
- 넓은 Table Row를 읽지 않음
비용
- Index Entry 폭 증가
- Leaf Block·Segment 증가
- Buffer Cache 점유
- INSERT·DELETE 비용
- Index Column UPDATE
- Redo·Undo·Backup
- Block Split·Hot Leaf
- 다른 SQL Plan 변화
Table Access 0
≠ 전체 비용 0
넓은 Index의 Leaf Scan과 DML 비용이 커질 수 있습니다.
10. 다른 Table Access 최소화 대안
10.1 불필요한 SELECT Column 제거
필요 없는 Column을 제거하면 기존 Index가 Covering이 될 수 있습니다.
-- 불필요한 SELECT *
SELECT order_id, amount
...
10.2 Index Join
여러 기존 Index가 Query의 모든 Column을 제공하면 ROWID Hash Join으로 Table Access를 제거할 수 있습니다.
VIEW index$_join$
HASH JOIN
INDEX RANGE SCAN
INDEX FAST FULL SCAN
여러 Index Scan·Hash 비용을 비교합니다.
10.3 Partition Pruning
필요 Partition만 Scan해 Table·Index 범위를 줄입니다.
PARTITION RANGE SINGLE
TABLE ACCESS FULL
전체 Table 크기가 아니라 Pruning 후 Block을 비교합니다.
10.4 Full Scan
후보 ROWID가 매우 많고 Table Block 대부분을 방문한다면 Full Scan이 더 저렴할 수 있습니다.
Index + Random Table Access
vs
Table Full·Parallel Scan
10.5 Top-N·Paging
정렬된 Index에서 첫 N행을 얻고 조기 중단하면 Table Access를 크게 줄일 수 있습니다.
INDEX RANGE SCAN DESCENDING
+ STOPKEY
11. 실행계획과 Runtime 통계
SELECT /*+ GATHER_PLAN_STATISTICS */
o.order_id,
o.customer_id,
o.amount
FROM orders o
WHERE o.status='READY'
AND o.region='SEOUL';
SELECT *
FROM TABLE(
DBMS_XPLAN.DISPLAY_CURSOR(
:sql_id,
:child_no,
'ALLSTATS LAST +PREDICATE +ALIAS +NOTE'
)
);
V$SQL_PLAN_STATISTICS_ALL과 연결되는 주요 통계입니다.
| 통계 | 의미 |
|---|---|
LAST_STARTS | 마지막 실행의 Row Source 시작 횟수 |
LAST_OUTPUT_ROWS | 마지막 실행에서 상위로 반환한 Row |
LAST_CR_BUFFER_GETS | Consistent Mode Buffer Get |
LAST_CU_BUFFER_GETS | Current Mode Buffer Get |
LAST_DISK_READS | Physical Read Block |
11.1 비교 순서
1. Index A-Rows·Buffers·Reads
2. Table A-Rows·Buffers·Reads
3. Access·Index Filter·Table Filter 위치
4. Starts와 반복 Probe
5. 최종 Row·Fetch 범위
6. Sort·TEMP·Partition·Parallel
7. CPU·Elapsed·P95
11.2 Buffers 중복 합산 주의
상위 Operation의 Buffers는 하위 Row Source 작업을 포함할 수 있습니다.
잘못된 방법
Plan 모든 Line의 Buffers 단순 합산
권장 방법
Index·Table Branch의 비용 집중 위치 확인
+ Statement 전체 총량 확인
12. 동일 조건 비교 계약
인덱스 개선 전후에는 다음을 맞춥니다.
- 동일 SQL 결과
- 동일 Bind 값과 Data Type
- 동일 Fetch 범위·End-of-Fetch
- 동일 통계·Optimizer 환경
- 동일 동시성·Parallel 설정
- 유사한 Cache 상태
- 대표값·편중값 반복
- DML·Redo·공간 측정
첫 20행 Fetch
vs
전체 Fetch
→ 직접 비교 불가
13. 진단 예제
변경 전
INDEX RANGE SCAN ORDERS_STATUS_IX
A-Rows 100,000
Buffers 500
TABLE ACCESS BY INDEX ROWID BATCHED
A-Rows 1,000
Buffers 70,000
후보 A: Filter Column 추가
Index (status,region)
INDEX RANGE SCAN
A-Rows 1,000
Buffers 30
TABLE ACCESS
A-Rows 1,000
Buffers 700
후보 B: Covering
Index (status,region,order_id,customer_id,amount)
INDEX RANGE SCAN
A-Rows 1,000
Buffers 100
Table Access 없음
후보 C: Full Scan
TABLE ACCESS FULL
A-Rows 1,000
Buffers 60,000
Parallel DOP 8
결론은 단순 Buffer 수만으로 내리지 않습니다.
- 후보 A는 DML 증가가 작을 수 있음
- 후보 B는 Query가 빠르지만 Index가 넓음
- 후보 C는 Parallel Resource를 많이 사용 가능
- 실행 빈도와 SLA를 포함해 결정
14. 자주 혼동하는 판단
| 혼동 | 정확한 기준 |
|---|---|
| Index가 사용되면 Table Access도 적다 | 후보 ROWID와 Table Buffers를 확인한다 |
| ROWID를 알면 I/O 비용이 없다 | Logical I/O·CPU와 Cache Miss 비용이 남는다 |
| Starts=1이면 Table을 한 번 읽었다 | Row Source 시작 횟수일 뿐 ROWID 수가 아니다 |
| BATCHED면 후보 ROWID가 줄어든다 | 접근 순서를 개선할 뿐 후보 수는 같다 |
| 후보 10,000·최종 50이면 처리량은 50이다 | 10,000 Table 후보를 처리했다 |
| Clustering Factor는 Table 하나의 값이다 | 각 Index별 통계다 |
| Index Column 추가는 항상 Range를 줄인다 | 첫 Range 뒤 Column은 Filter 효과만 날 수 있다 |
| Covering은 무조건 최적이다 | 넓은 Index의 Scan·DML·공간을 비교한다 |
| Cache Hit이면 Random Access 비용이 없다 | Logical I/O와 CPU가 남는다 |
| Table Access 제거만이 유일한 해법이다 | Full Scan·Partition·Index Join도 비교한다 |
개념 확인 문제
문제를 누르면 바로 아래에서 정답과 해설을 확인할 수 있습니다.
01Index Scan과 TABLE ACCESS BY INDEX ROWID의 역할을 비교하시오.
Index Scan·Table Access
- Index Scan은 Key 조건을 이용해 Index Entry와 후보 ROWID를 찾습니다.
- Table Access는 ROWID가 가리키는 Heap Table Row를 읽고 남은 Filter와 반환 Column을 처리합니다.
- Index가 필요한 모든 Column을 제공하면 Table Access가 생략될 수 있습니다.
02Extended Physical ROWID의 네 구성요소와 변경 가능성을 설명하시오.
ROWID
- Data Object Number, Relative File Number, Data Block Number, Row Number를 포함합니다.
- Row Movement, Shrink, Flashback, Export·Import 등에서 바뀔 수 있습니다.
- 영구 업무 Key로 사용하지 않습니다.
03후보 ROWID·방문 Table Block·최종 Row를 분리해야 하는 이유를 설명하시오.
세 단계
- Index A-Rows는 Table로 전달한 후보 규모를 보여 줍니다.
- 같은 후보 수라도 ROWID Block 분산에 따라 Table Buffers가 다릅니다.
- Table A-Rows는 늦은 Filter 후 최종 Row를 보여 줍니다.
- 세 단계를 분리해야 낭비가 Index·ROWID·Table 중 어디에 있는지 알 수 있습니다.
04TABLE ACCESS BY INDEX ROWID BATCHED의 목적과 한계를 설명하시오.
BATCHED
- 몇 개의 ROWID를 모아 Block 순서에 가깝게 Row를 읽으려는 방식입니다.
- 같은 Block 반복 방문을 줄일 수 있습니다.
- 후보 ROWID 수나 Table Access 자체를 줄이지는 않습니다.
05Clustering Factor가 Table Random Access 비용에 미치는 영향을 설명하시오.
Clustering Factor
- 낮은 방향은 Index Key 순서와 Table Block 배치가 비슷해 Range Scan의 Table 방문이 적을 수 있습니다.
- Row 수에 가까운 높은 값은 ROWID가 흩어진 방향을 나타내 Random Access가 커질 수 있습니다.
- 각 Index별 통계입니다.
06Row Migration·Chaining이 ROWID Access 비용을 늘리는 이유를 설명하시오.
Migration·Chaining
- 하나의 ROWID를 따라간 뒤 다른 Block의 Row Piece를 추가로 읽을 수 있습니다.
- 후보 ROWID 수보다 Table Buffer가 비정상적으로 커질 수 있습니다.
- Row 폭·Update 패턴과 실제 영향을 확인합니다.
07Index Column 추가의 Range 축소·Index Filter·Covering 효과를 비교하시오.
세 효과
- Range 축소: Start·Stop Key를 좁혀 Leaf Entry와 후보 ROWID를 모두 줄입니다.
- Index Filter: Leaf Range는 남지만 Table 방문 전에 후보를 제거합니다.
- Covering: 조건·반환 Column을 모두 Index에서 얻어 Table Access를 제거합니다.
08Covering Index의 조회 이득과 DML·공간 비용을 설명하시오.
Covering Trade-off
- Table Buffers·Physical Read·Clustering 영향을 제거할 수 있습니다.
- Index Entry·Leaf Block·Segment·Cache가 커집니다.
- INSERT·DELETE와 포함 Column Update의 Redo·Undo·DML 비용이 증가합니다.
09Index Join·Partition Pruning·Full Scan이 Table Access 최소화 대안이 되는 조건을 설명하시오.
다른 대안
- Index Join은 여러 Index가 Query를 Cover할 때 Table Access를 제거할 수 있습니다.
- Partition Pruning은 필요한 Partition만 읽어 범위를 줄입니다.
- 후보가 많아 Table Block 대부분을 방문하면 Full Scan이 더 효율적일 수 있습니다.
- Query 패턴과 Workload 비용을 비교합니다.
10Runtime 통계로 개선 전후를 공정하게 검증하는 절차를 설명하시오.
검증 - 동일 결과·Bind Type·Fetch·통계·동시성을 맞춥니다. - Index와 Table의 Starts·A-Rows·Buffers·Reads를 분리합니다. - Predicate 위치, Sort·TEMP·Partition·Parallel을 확인합니다. - CPU·Elapsed·P95와 신규 Index DML·Redo·공간을 대표·편중값으로 반복 측정합니다.