본문 바로가기

Data Science/SQLP

[SQLP] 3-1. 테이블 액세스 최소화

반응형

SQL 성능 향상을 위한 느린 랜덤 I/O 극복하기

-> 테이블 램덤 액세스가 성능에 미치는 영향과 그에 따른 테이블 랜덤 액세스를 최소화하는 방법


1. 테이블 랜덤 액세스

 

인덱스에 대한 맹신 또는 섣부른 자신감

 

1. 대용량의 테이블에서 인덱스를 사용해 조회시 빠르다.

2. 대량 데이터 조회시 인덱스 사용하면 테이블 전체 스캔보다 느리다.

 

인덱스 ROWID의 실체 -> 물리적 주소와 논리적 주소

: 디스크 상에서 테이블 레코드를 찾아가기 위한 위치 정보를 담은 논리적 주소

 

인덱스를 이용해 테이블 액세스하는 실행계획

SELECT * FROM 고객 WHERE 지역 = '서울';

 

Execution Plan
-----------------------------------------------------------------------
SELECT STATEMENT Optimizer=ALL_ROWS
 TABLE ACCESS BY INDEX ROWID OF '고객' (TABLE)
  INDEX RANGE SCAN OF '고객_지역_IDX' (INDEX)

-> SQL이 참조하는 칼럼을 인덱스가 모두 포함하는 경우가 아니면, 인덱스를 스캔한 후에 반드시 테이블 액세스

[TABLE ACCESS BY INDEX ROWID 부분]

 

인덱스 스캔하는 이유

-> 검색 조건을 만족하는 소량의 데이터를 인덱스에서 빨리 찾고 거기서 테이블 레코드를 찾아가기 위한 주소값

ROWID 얻기 위해

 

논리적 주소인 ROWID

: 물리적으로 직접 연결되어 있는 것이 아닌 테이블 레코드를 찾아가기 위한 논리적 주소 정보를 담고 있다.

 


메인 메모리 DB와 비교 (MMDB; Main Memory DB)

: 데이터를 모두 메모리에 로드해 놓고 메모리를 통해서만 I/O를 수행하는 DB

 

버퍼캐시 히트율이 99퍼 이상으로 유지하는 잘 튜닝된 OLTP 시스템

-> 디스크를 경유하지 않고 대부분 데이터를 메모리에서 읽는다.

-> 메인 메모리 DB만큼 빠르지 않고, 대량 데이터를 인덱스로 액세스할 때엔 차이가 두드러진다.

 

디스크를 경유하지 않고 대부분 데이터를 메모리에서 읽어도 MMDB보다 느린 이유

-> 인스턴스를 기동하면 디스크에 저장된 데이터를 버퍼캐시로 로딩하고 이어서 인덱스 생성

 

[인덱스는 오라클처럼 디스크 상의 주소정보를 갖는 게 아니라 메모리상의 주소정보, 즉 포인터를 갖는다.]

-> 인덱스 경유해 테이블 액세스 하는 비용이 오라클과 비교할 수 없을 정도로 낮다.

 

오라클의 경우 테이블 블록이 수시로 버퍼캐시에 밀려났다가 다시 캐싱되며, 그때마다 다른 공간에 캐싱되기 때문에

인덱스에서 포인터로 직접 연결 불가능한 구조

-> 즉, 메모리 주소 정보(포인터)가 아닌 디스크 주소 정보(DBA)를 이용해 해시 알고리즘으로 버퍼 블록을 찾아간다.

-> 인덱스 ROWID를 이용한 테이블 액세스가 생각보다 빠르지 않은 이유


I/O 메커니즘 복습

 

DBA(데이터파일번호+블록번호) : 디스크 상에서 블록을 찾기 위한 주소 정보

-> I/O 성능을 높이기 위해 버퍼캐시 활용 [디스크에서 블록을 읽어들이는 것을 줄이기 위해]

-> 블록을 읽을 때 버퍼캐시에서 찾아보고 없으면 디스크로 가서 찾는다.

 

버퍼캐시에서 찾는 과정

-> 읽고자 하는 DBA해시 함수에 입력해서 해시 체인을 찾고 버퍼 헤더를 찾는다.

[DBA -> 해시 함수 -> 해시 체인 -> 버퍼 헤더]

 

버퍼 헤더는 항상 같은 해시 체인에 연결 [캐시 적재할 때와 읽을 때 같은 해시 함수를 사용하기 때문에]

-> 버퍼 블록은 매번 다른 위치에 캐싱, 그 메모리 주소값을 버퍼 헤더가 가지고 있다.

[해싱 알고리즘을 이용해 버퍼 헤더를 찾아서,  거기서 얻은 포인터로 버퍼 블록을 찾는다.]

 

인덱스로 테이블 블록 액세스

-> 리프 블록에서 읽은 ROWID를 분해해 DBA 정보를 얻고, 테이블을 Full Scan할 때 익스텐트 맵을 통해
읽을 블록들의 DBA 정보를 얻는다.

 

I/O 메커니즘 1. ROWID가 가리키는 테이블 블록을 버퍼캐시에서 찾는다.
2. 못 찾을 경우 디스크에서 블록을 읽어서 버퍼캐시에 적재
3. 블록을 읽는다.

모든 데이터가 캐싱되어 있을 경우

-> 그럼에도 테이블 레코드를 찾기 위해 매번 DBA 해싱래치 획득 과정 반복

-> 동시 액세스가 심할 경우 캐시버퍼 체인 래치버퍼 Lock에 대한 경합 발생

-> 즉, 인덱스 ROWID를 이용한 테이블 액세스는 고비용 구조

 


우편주소에 해당하는 인덱스 ROWID

ROWID를 우편주소에, 메인메모리 DB가 사용하는 포인터를 전화번호로 비유

-> 봉투에 적힌 대로 우체부 아저씨가 일일이 찾아다니는 구조

 

오라클에서 하나의 레코드를 찾아가는 데 있어 가장 빠르다고 알려진 'ROWID에 의한 테이블 액세스'  [고비용 연산]

SELECT * FROM 고객 WHERE 지역 ='서울';

 

Execution Plan
-----------------------------------------------------------
SELECT STATEMENT Optimizer=ALL_ROWS
 TABLE ACCESS (BY INDEX ROWID) OF '고객' (TABLE)
  INDEX (RANGE SCAN) OF '고객_지역_IDX' (INDEX)

-> 앞으로 실행계획에서 아래와 같이 'TABLE ACCESS BY INDEX ROWID' 오퍼레이션에 대한 처리과정

 


2. 인덱스 클러스터링 팩터 (Clustering Factor; CF)

: 군집성 계수; 특정 칼럼 기준으로 같은 값을 갖는 데이터가 서로 모여있는 정도

[CF가 좋은 칼럼에 생성한 인덱스는 검색 효율이 좋다.

-> 조건에 해당하는 레코드가 물리적으로 근접해 있을 때가 흩어져 있을 때보다 데이터를 찾는 속도가 빠르다.]

 

-> 인덱스 레코드 정렬 순서와 테이블 레코드 정렬 순서가 일치할수록 좋다.

-> 따라서, CF가 좋은 칼럼에 생선한 인덱스는 검색 효율이 좋다.

-> 테이블 액세스량에 비해 블록 I/O가 적게 발생하기 때문에

 

 

CF가 좋은 인덱스를 사용할 때 테이블 액세스 횟수에 비해 블록 I/O가 적게 발생하는 이유

-> 버퍼 Pinning

:오라클은 찾아간 테이블 블록에 대한 포인터 (메모리에 대한 주소값)을 바로 해제하지 않고 유지

 

인덱스 레코드를 읽었을 때 직전과 같은 테이블 블록을 가리킬 때 버퍼 Pinning을 이용해 바로 데이터 블록을 읽을 수 있다.

-> 논리적인 블록 I/O 과정 생략하기 때문에

 

[CF가 안 좋은 인덱스 사용시 테이블 액세스 횟수 만큼 블록 I/O가 발생]

 


3. 인덱스 손익분기점

추출건수에 대한 소요시간 그래프에서

Table Access By Index Scan은 우상향 직선 [추출건수가 늘어날수록 성능이 줄어들기 때문]

Full Table Scan은 상수 함수 [추출건수에 상관없이 성능이 일정하기 때문]

-> 손익분기점은 둘이 교차하는 지점이며, Index Range Scan에 의한 테이블 엑세스가 Table Full Scan보다 느려지는 지점

 

인덱스 ROWID를 이용한 테이블 액세스는 고비용 구조이기 때문에,
읽어야 할 데이터가 일정량을 넘을 때 테이블 전체를 스캔하는 것보다 느려진다.

 

인덱스 ROWID를 이용한 테이블 액세스가 Table Full Scan보다 느려지게 하는 이유

Table Full Scan은 시퀀셜 액세스, Multiblock I/O 인덱스 ROWID는 랜덤 액세스 방식, Single Block I/O 방식

-> CF에 따라 달라진다.

->인덱스 손익분기점은 평균적으로 5~20%이며, CF가 좋을수록 인덱스 손익 분기점이 높아져 90퍼센트까지 상승한다.

[대용량의 테이블의 경우에는 손익분기점이 더 낮아진다. -> 조회 건수가 늘어난 양에 비해 성능이 더 느려진다.

-> 조회 건수가 늘수록 데이터를 버퍼캐시에서 찾을 가능성이 작아지기 때문에

 

특정 테이블을 인덱스로 대용량건을 액세스한다면 캐시 히트율은 매우 낮아진다.

-> 대용량 테이블인 경우 인덱스 칼럼 기준을 값이 같은 테이블 레코드가 근처에 모여있을 가능성이 적으므로,

인덱스를 스캔하면서 읽은 테이블 블록을 뒤에서 다시 읽을 가능성이 작아서 거의 모든 데이터를 디스크에서 읽는다.

 

-> 이런 경우 손익분기점이 의미가 없으며, Table Full Scan방식으로 읽는게 빠를 수 있다.

[시퀀셜 액세스와 Multiblock I/O 방식]

 

-> 온라인 프로그램과 배치 프로그램 튜닝의 특징을 구분 짓는 개념


온라인 프로그램 튜닝 vs 배치 프로그램 튜닝

온라인 프로그램 튜닝 배치 프로그램 튜닝
인덱스 활용 필요 [주로 소량 데이터를 읽고 갱신하기 때문에]
주로 NL 조인 방식 [인덱스를 이용하는 조인방식이기 때문에]

인덱스 이용해 소트 연산 생략해 부분범위 처리 방식으로 구현 -> 대량 데이터 조회시에도 빠른 응답속도
전체범위 처리 기준 튜닝 [대량 데이터를 읽고 갱신하므로]
전체를 빠르게 처리하는 게 목표
Full Scan과 해시 조인 방식 [대량 데이터 빠르게 처리 위해]

 

실명확인번호로 조회한 특정 고객의 최근 1년 이내 변경 이력 중 전월 말일 데이터 출력 쿼리

SELECT C.고객번호, C.고객명, H.전화번호, H.주소, H.상태코드, H.변경일시
FROM 고객 C, 고객변경이력 H
WHERE C.실명확인번호 = :rmnno
AND H.고객번호 = C.고객번호
AND H.변경일시 =(SELECT MAX(변경일시)
				FROM  고객변경이력 M
                WHERE 고객번호 = C.고객번호
                AND   변경일시 >=trunc(add_months(sysate, -12), 'mm')
                AND   변경일시 < trunc(sysdate, 'mm'))

 

Execution Plan
-----------------------------------------------------------
SELECT STATEMENT Optimizer=ALL_ROWS
 NESTED LOOPS
  NESTED LOOPS
   TABLE ACCESS (BY INDEX ROWID) OF '고객' (TABLE)
    INDEX (RANGE SCAN) OF '고객_X01' (INDEX)
   VIEW PUSHED PREDICATE OF 'SYS.VW_SQ_1' (VIEW)
    SORT (AGGREGATE)
     FIRST ROW
      INDEX (RANGE SCAN (MIN/MAX)) OF '고객변경이력_PX'
   INDEX (UNIQUE SCAN) OF '고객변경이력_PK' (INDEX (UNIQUE))
  TABLE ACCESS (BY INDEX ROWID) OF '고객변경이력' (TABLE)

-> 실명확인번호 조건에 해당하는 데이터는 한 건 혹은 소량이기 때문에 인덱스와 NL 조인을 사용하는 것이 효과적

 

 

고객구분코드가 'A001'인 고객의 최근 1년 이내 변경 이력 중 전월 말일 데이터를 읽어 고객_임시 테이블에 입력

INSERT INTO 고객_임시
SELECT C.고객번호, C.고객명, H.전화번호, H.주소, H.상태코드, H.변경일시
FROM 고객 C, 고객변경이력 H
WHERE C.고객구분코드 = 'A001'
AND H.고객번호 = C.고객번호
AND H.변경일시 = (SELECT MAX(변경일시)
                FROM 고객변경이력 = C.고객번호
                AND 변경일시 >= trunc(add_months(sysdate, -12), 'mm')
                AND 변경일시 < trunc(sysdate, 'mm'))

-> 전체 300만명 중 조건을 만족하는 고객은 100만명

-> 조건절만 바꿀 경우 심한 성능 저하

 

INSERT INTO 고객_임시
SELECT /*+ full(c) full(h) index_ffs(m.고객변경이력)
		ordered no_merge(m) use_hash(m) use_hash(h) */
	C.고객번호, C.고객명, H.전화번호, H.주소, H.상태코드, H.변경일시
FROM 고객 C
	,(SELECT 고객번호, MAX(변경일시) 최종변경일시
     FROM 고객변경이력
     WHERE 변경일시 >= trunc(add_months(sysdate, -12), 'mm')
     AND 변경일시 < trunc(sysdate, 'mm')
     GROUP BY 고객번호) M
     , 고객변경이력 H
WHERE C.고객구분코드 = 'A001'
AND M.고객번호 = C.고객번호
AND H.고객번호 = M.고객번호
AND H.변경일시 = M.최종변경일시

-> 고객변경이력 테이블을 2번 읽는다. [비효율]

 

윈도우 함수 이용

INSERT INTO 고객_임시
SELECT 고객번호, 고객명, 전화번호, 주소, 상태코드, 변경일시
		(SELECT /*+ full(c) full(h) index_ffs(m.고객변경이력)
		ordered no_merge(m) use_hash(m) use_hash(h) */
		C.고객번호, C.고객명, H.전화번호, H.주소, H.상태코드, H.변경일시
                , rank() over (partition by H.고객번호 ORDER BY H.변경일시 desc) no
    FROM 고객 C, 고객변경이력 H
    WHERE C.고객구분코드 = 'A001'
    AND H.변경일시 >= trunc(add_months(sysdate, -12), 'mm')
    AND M.고객번호 <  trunc(sysdate, 'mm')
    AND H.고객번호 = C.고객번호)
WEHRE no=1

 

 

Execution Plan
---------------------------------------------------------------
INSERT STATEMENT Optimizer=ALL_ROWS
 LOAD TABLE CONVENTIONAL OF '고객_임시'
  VIEW
   WINDOW (SORT PUSEHD RANK)
    FILTER
     HASH JOIN
      TABLE ACCESS (FULL) OF '고객' (TABLE)
      TABLE ACCESS (FULL) OF '고객변경이력' (TABLE)

-> 대량 배치 프로그램 : 인덱스 < FULL SCAN [효율성]

-> 하지만, 초대용량 테이블에서 FULL SCAN은 시간이 오래걸리고, 과부하를 준다

 

해결 -> 파티션 활용 전략 + 병렬 처리

: 고객변경이력 테이블에서 변경일시 기준 파티셔닝 [변경일시 조건 (최근 1년)에 해당하는 파티션만 Full Scan]

 

월 단위로 파티션한 테이블에서 틀정 월 또는 몇 개월 치 데이터 조회시 : 인덱스 < FULL SCAN [효율성]

 

즉, 데이터를 파티션하는 이유는 Full Scan을 처리하기 위해서

 


4. 인덱스 칼럼 추가

: 테이블 액세스 최소화을 하는 가장 일반적인 기법

 

EMP 테이블에 현재 PK 이외에 [DEPTNO + JOB] 순으로 구성한 EMP_X01 인덱스 하나만 있는 상태에서 쿼리 수행

SELECT /*+ index(emp emp_x01)*/ *
FROM EMP
WHERE deptno = 38
AND sal>= 2000

-> 인덱스 구성 변경이 어렵기 때문에

 

SELECT *
FROM EMP
WHERE deptno = 30
AND job='CLERK'

[기존 인덱스를 사용하는 SQL 존재하기 때문에]

 

 

인덱스를 추가 하는 것은 관리에 부담이 있고, DML 부하에 따른 트랜잭션 성능 저하 발생 가능하다.

해결 -> 인덱스에 칼럼 추가

 

기존 인덱스에 칼럼을 추가한다면, 인덱스 스캔량은 줄지 않지만, 테이블 랜덤 액세스 횟수 감소

SELECT 렌탈관리번호, 고객명, 서비스관리번호, 서비스번호, 예약접수일시
	, 방문국가코드1, 방문국가코드2, 방문국가코드3, 로밍승인번호, 자동로밍여부
FROM 로밍렌탈
WHERE 서비스번호 LIKE '010%'
AND 사용여부 = 'Y';
Rows Row Source Operation
---- ----------------------------
1989   TABLE ACCESS BY INDEX ROWID 로밍렌탈 (cr=266968 pr=27830 pw=0 time=...)
266476  INDEX RANGE SCAN 로밍렌탈_N2 (cr=1011 pr=900 pw=0 time=1893462 us)

'서비스번호' 단일 칼럼으로 구성된 인덱스 (로밍렌탈_N2) -> 스캔해서 얻은 건수 266,476

그 건수만큼 테이블 랜덤 액세스 -> 265,957개 블록을 읽음 [전체 블록 I/O의 99.6%, 소요시간 49초 (cr)]

하지만, 테이블 방문에 비해 최종 결과집합이 매우 적다. [사용여부 ='Y' 체크 과정에서 여과]

 


인덱스 클러스터링 팩터 효과

: 클러스터링 팩터가 좋은 인덱스를 이용해 테이블 액세스량에 비해 블록 I/O를 적게 발생

-> 테이블액세스와 블록 I/O 발생 수가 큰 차이가 없기 때문에, 인덱스 클러스터링 팩터가 매우 안좋다

즉, 데이터양이 매우 많아 서비스번호 조건을 만족하는 데이터가 뿔뿔이 흩어져 있다.


테이블 방문에 비해 최종 결과집합이 적은 문제 해결

: 로밍렌탈_N2 인덱스에 '사용여부' 칼럼 추가 후 SQL 트레이스 결과

Rows Row Source Operation
---- ----------------------------
1989   TABLE ACCESS BY INDEX ROWID 로밍렌탈 (cr=2902 pr=0 pw=0 time=...)
1909    INDEX RANGE SCAN 로밍렌탈_N2 (cr=1011 pr=900 pw=0 time=198557 us)

-> 인덱스를 거쳐 방문한 테이블 횟수와 같이 모두 결과집합에 포함

-> 불필요한 테이블 액세스 발생하지 않음 -> 총 블록 I/O가 2902개로 감소

 

즉, 테이블 액세스 단계 필터 조건에 의해 버려지는 레코드가 많을 때, 인덱스에 칼럼 추가할 경우 성능 향상 효과


5. 인덱스만 읽고 처리

: 테이블 랜덤 액세스가 많아도 필터 조건에 의해 버려지는 레코드가 없을 때 비효율은 발생하지 않을 경우

 

아래 쿼리에 부서번호 단일 칼럼으로 구성된 인덱스를 사용한다면, 비효율은 발생하지 않는다.

SELECT 부서번호, SUM(수량)
FROM  판매집계
WHERE 부서번호 LIKE '12%'
GROUP BY 부서번호;

-> 인덱스에서 부서번호 LIKE 조건에 해당하는 데이터를 찾고 테이블 액세스 후 버리는 데이터가 없다.

-> 인덱스 스캔 과정에서 얻은 데이터가 많아 테이블 내덤 액세스가 많이 발생해 성능이 느림

 

해결

1. 쿼리에 사용된 칼럼을 모두 인덱스에 추가[Covered 인덱스를 이용한 Covered 쿼리]

2. '부서번호' 단일 칼럼으로 구성된 인덱스에 '수량' 칼럼만 추가 [추가할 칼럼이 많지 않을 때 사용]

-> 둘 다 테이블 액세스를 제거해 성능 향상시킴

 

 


Include 인덱스

: SQL SERVER에만 있는 인덱스

: 인덱스 키 외에 미리 지정한 칼럼을 리프 레벨에 함께 저장하는 기능

 


6. 인덱스 구조 테이블

: 고비용인 인덱스를 이용한 테이블 액세스를 해결하기 위해 랜덤 액세스가 아예 발생하지 않도록 테이블을 인덱스 구조로 생성

 

-> IOT (Index-Oraganized Table)

 

인덱스 : 테이블을 찾아가기 위한 ROWID를 갖는다.

IOT : 그 자리에 테이블 데이터를 갖는다.

-> 테이블 블록에 있어야할 데이터를 인덱스 리프 블록에 모두 저장 [인덱스 리프 블록이 곧 데이터 블록]

 

 

테이블을 인덱스 구조로

create table index_org_t (a number, b varchar(10)
			, constraint index_org_t_pk primary key (a) )
organization index;

 

 

일반 테이블은 힙 구조 테이블로 index 옵션이 아니라 heap 옵션을 사용 [기본값으로 생략 가능]

create table index_org_t (a number, b varchar(10)
			, constraint index_org_t_pk primary key (a) )
organization heap;

 

일반 테이블 -힙 구조 테이블, 데이터 입력시 랜덤 방식 사용
-Freelist로부터 할당 받은 블록에 정해진 순서 없이 데이터 입력
인덱스 구조 테이블 -IOT (Index-Organized Table), 랜덤 액세스 발생 X, 해당 테이블 데이터를 갖는다.
-정렬 상태를 유지하며 데이터 입력
-인위적으로 클러스터링 팩터를 좋게 만드는 방법 [시퀀셜 방식 데이터 액세스]
-BETWEEN이나 부등호 조건으로 넓은 범위를 읽을 때 유리
-데이터 입력과 조회 패턴이 서로 다른 테이블에도 유용

 

데이터 입력과 조회 패턴이 서로 다른 테이블에도 유용한 예

:영업사원 100명, 한 블록에 100개 레코드가 담기는 영업사원들의 일별 실적 집계 테이블

 

-> 매일 한 블록씩 1년에 365개의 블록

-> 실적등록은 일자별 진행, 실적조회는 사원별로 이루어짐

 

-> 다음은 가장 많이 수행하는 쿼리

SELECT substr(일자, 1, 6) 월도
	,sum(판매금액) 총판매금액, avg(판매금액) 평균판매금액
FROM 영업실적
WHERE 사번='S1234'
AND 일자 BETWEEN '20180101' AND '20181231'
GROUP BY substr(일자, 1, 6)

-> 이 쿼리에 인덱스를 사용시, 사원마다 랜덤 액세스 방식으로 365개 테이블 블록을 읽어야 하는데,

-> 클러스터링 팩터가 매우 안좋아 조회 건수만큼 블록 I/O 발생

 

입력과 조회 패턴이 다른 경우

해결 -> 사번이 첫 번째 정렬 기준이 되도록 IOT 구성

CREATE TABLE 영업실적 (사번 VARCHAR2(5), 일자 VARCHAR2(8), ...
 , CONSTRAINT 영업실적_PK PRIMARY KEY (사번, 일자) ) ORGANIZATION INDEX;

-> 한 블록에 100개 레코드가 담기므로 네 개 블록만 읽고 처리 가능


7. 클러스터 테이블

:인덱스 클러스터와 해시 클러스터

 

(1) 인덱스 클러스터 테이블

: 클러스터 키값이 같은 레코드를 한 블록에 모아서 저장하는 구조

-> 한 블록에 모두 담을 수 없을 때는 새로운 블록을 할당해서 클러스터 체인으로 연결

 

여러 테이블 레코드를 같은 블록에 저장할 경우

-> 다중 테이블 클러스터 [일반 테이블의 경우 하나의 데이터 블록을 여러 테이블이 공유 불가]

-> 오라클 클러스터는 키 값이 같은 데이터를 같은 공간에 저장해 둘 뿐, 정렬하지 않는다.

 

인덱스 클러스터 테이블 구성 과정

1. 클러스터 생성

CREATE CLUSTER c_dept# (deptno number(2) ) index;

2. 클러스터에 테이블 담기 전, 클러스터 인덱스 정의

[클러스터 인덱스는 데이터 검색, 데이터 저장될 위치 찾을 때 사용]

CREATE INDEX c_dpet#_idx on cluster c_dept#;

3. 클러스터 테이블 생성

CREATE TABLE dept (
 deptno number(2) not null
 , dname varchar2(14) not null
 , loc varchar(13) )
 cluster c_dept#(deptno);

 

 

 

클러스터 인덱스 B*Tree 인덱스 구조를 사용하는데, 해당 키 값을 저장하는 첫 번째 데이터 블록 가리킴, 테이블 레코드와 1:M 대응 관계 [키 값은 항상 Unique]
일반 테이블 인덱스 레코드 테이블 레코드를 일일이 가리킴, 테이블 레코드와 1:1 대응 관계

 

클러스터 인덱스를 스캔하면서 값을 찾을 때, 값 하나당 한 번씩 발생

[클러스터 체인을 스캔하면서 발생하는 랜덤 액세스 제외]

 

클러스터에 도달해서 시퀀셜 방식으로 스캔 -> 넓은 범위를 읽더라도 비효율이 없다.

 

인덱스 클러스터 조회

SELECT *
FROM DEPT
WHERE deptno= :deptno;

 

Execution Plan
---------------------------------------------------------------
SELECT STATEMENT Optimizer=ALL_ROWS
 TABLE ACCESS (CLUSTER) OF 'DEPT' (CLUSTER)
  INDEX (UNIQUE SCAN) OF 'C_DEPT#_IDX' (INDEX (CLUSTER))

(2) 해시 클러스터 테이블

: 인덱스를 사용하지 않고 해시 알고리즘을 사용해 클러스터를 찾아간다.

 

해시 클러스터 테이블 구성 과정

1. 클러스터 생성

CREATE CLUSTER c_dpet# (deptno number(2) ) hashkeys 4;

 

2. 클러스터 테이블 생성

CREATE TABLE DEPT(
   deptno number(2) not null
 , dname varchar(2) not null
 , loc   varchar2(13)
 cluster c_dept# (deptno);

 

해시 클러스터 조회

SELECT *
FROM DEPT
WHERE deptno = :deptno;

 

Execution Plan
---------------------------------------------------------------
SELECT STATEMENT Optimizer=ALL_ROWS
 TABLE ACCESS (HASH) OF 'DEPT' (CLUSTER (HASH))
반응형