<새로운 인덱스 생성의 의미>
[VLDB: 인덱스 선정을 통한 엑세스 형태의 효율화를 위한 조언 ]
\- 적은 데이터를 가진 소형 테이블
\- 주로 참조되는 역할을 하는 중대형 테이블
\- 업무의 구체적인 행위를 관리하는 중대형 테이블
\- 저장용 대형 테이블
가) 적은 데이터를 가진 소형 테이블
- 적은 데이터의 의미: 한번의 다중 블록 I/O Call로 모든 데이터를 읽을 수 있는 경우((DB_FILE_MULTIBLOCK_READ_COUNT의 값보다 적은 테이블)
\- 한번의 다중 블록 I/O Call로 모든 내용을 읽을 수 있으므로, 인덱스를 통한 스캔과 거의 차이가 없음. 그럼에도 불구하고 생성해야 하는가?
\-> 인덱스 유무는 옵티마이저의 판단에 많은 영향을 미치는데 특히 조인과 인덱스는 옵티마이저의 결정에 큰 영향을 미침
인덱스는 가장 큰 영향요소 이므로 존재 여부에 따라 실행 계획에 큰 영향을 미칠 수 있음.
해당 유형의 인덱스는 Nested Loop Join시 내측루프를 주로 수행하므로 많은 횟수가 수행될 수 있음. 약간의 차이가 전체적으로 큰 영향을 미칠 수 있음.
\- 권고
작은 테이블이라도 PK는 반드시 생성을 권고하며, PK에 의해 참조가 많이 되는 테이블의 경우는 조인 시 효율을 위해 IOT 생성 검토도 필요
나) 주로 참조되는 역할을 하는 중대형 테이블
\- 의미: 트랜잭션 데이터들의 행위의 주체나 목적이 되는 개체들로 구성된 테이블들. 모델링에서 키 엔티티로 분류되는 집합임.
\- 특징: 좁은 범위의 스캔 또는 조인등에 의해 내측 루프에서 기본키에 의해 연결됨
검색 조건의 형태가 뚜렷하고, 데이터 증감이 별로 없으며, 검색 위주의 엑세스 발생
\- 권고
* 기존의 사용중인 인덱스 블록에 사용하는 비율이 낮으므로, 대량 입력 작업이나 오랜 시간이 지났다면 인덱스 Rebuild 권고
다) 업무의 구체적인 행위를 관리하는 중대형 테이블
\- 의미: 업무의 구체적인 내용을 담고 있는 테이블.
\- 특징: 조인에서 주로 외측 루프를 담당하고 있음.
테이블이 크고, 지속적으로 증가하고 있으므로 많은 인덱스는 입력 시 부하가 가게 하며, 인덱스 구성이 약간만 잘못 되어도 처리 범위가 증가할 수 있음.
다양한 엑세스 조건으로 인덱스의 수가 많고 복합 인덱스가 많으며, 다양한 연산자가 존재하여 결합 인덱스의 순서 또한 중요
\- 권고
* 인덱스 구성 전략에 따라서 시스템이 미치는 영향이 크므로 절차를 충실히 따라야 함.
이를 위해 테이블에 엑세스 하고 있는 모든 유형을 수집하고, 앞으로 예상되는 형태까지 감안하는 것이 필요
라) 저장용 대형 테이블
\- 의미: 로그성 데이터를 관리할 목적으로 생성된 테이블
\- 특징: 대량의 데이터를 가지고 있으며, 지속적으로 대량의 데이터가 입력됨.
\- 권고
* 갱신이 발생하지 않기 때문에 낮은 PCTFREE 값 지정하는 것이 공간 사용에 유리
* 기본키를 가지는 것은 입력 시 부담이 될 수 있으므로 사용하지 않는 것도 고려. 대신 Unique Index 생성 고려
* 테이블에 파티션을 만들고 파티션 마다 필요한 인덱스(Local Index)를 생성하는 것이 좋음. 오래된 데이터의 경우 파티션 단위로 떼어내기 쉽고 입력이나 검색 시 부담이 적어짐
* 파티션 인덱스 사용 시, 사용 파티션의 인덱스만 사용하는 것도 고려(전체 Local Index를 UNUSABLE 상태로 만든 후, 이후 필요한 파티션의 인덱스만 생성하는 방식)
\- 인덱스 생성의 목적: 전체 집합에서 범위를 줄여 선별된 부분만 엑세스 하고자 함
\- 분포도: 전체 집합에서 해당 컬럼으로 조회 시 해당 조건이 차지하는 비율을 의미.
\- 손익 분기점: 인덱스는 단일 블록 I/O를 유발하므로 분포도가 높은 경우에는 인덱스가 존재한다 할지라도 사용하지 않을 수 있다.
손익 분기점은 이렇게 인덱스 사용 여부를 결정하는 분포도 값이다. 옵티마이저는 손익분기점 이상이면 인덱스를 사용하지 않고 이하면 사용한다.
\- 인덱스의 분포도가 높으면 무조건 인덱스를 생성하지 않는 것이 유리한가?
\--> 반드시 그렇지 않다. 부분 범위 처리를 할 경우 실제 분포도는 아주 낮아질 수 있다.
\- 인덱스 머지(Index Merge): 여러 인덱스가 협력하여 같이 엑세스를 주관
* 장점: 처리 범위가 넓은 경우, 테이블에 엑세스 하여 Filter 하는 부분이 생략됨. 비교하는 조건으로 인덱스만 사용.
\- 결합 인덱스: 여러 컬럼을 모아 하나의 인덱스로 만드는 방식.
* 장점: 모든 조건이 인덱스 컬럼에 사용되는 경우 인덱스 머지에 비해 아주 효율적으로 수행될 수 있다.(단, 선두 컬럼의 Equal 연산에서만 적용)
\- Index Merge 방식과 결합 인덱스 방식의 연산 차이
< 결합 인덱스의 특징 >
가) 분포도와 결합 순서의 상관 관계
ex) 구성
\- 테이블: TAB1, 컬럼: COL1, COL2
\- 컬럼 COL1: 분포도가 좋지 않음
\- 컬럼 COL2: 분포도가 좋음
\- 사용된 SQL
SELECT * FROM TAB1 WHERE col1='A' AND col2='112' |
---|
\- 엑세스 방식에 따른 구분
\* 분포도가 좋지 않은 COL1 컬럼을 복합인덱스의 선행 컬럼으로 설정한 경우 처리 과정(왼쪽 그림)
1. col1='A' AND col2=112 인 데이터를 B-Tree 방식으로 바로 찾음
2. ROWID를 이용하여 테이블 로우 엑세스
3. 다음 로우를 탐색하여 조건에 만족하면 다시 테이블의 로우를 엑세스하고 그렇지 않으면 스캔 종료(이 경우, 한 번의 랜덤, 2개의 인덱스 로우 스캔)
\* 분포도가 좋은 COL2 컬럼을 복합인덱스의 선행 컬럼으로 설정한 경우 처리 과정(오른쪽 그림)
1. col2=112 AND col1='A' 인 데이터를 B-Tree 방식으로 바로 찾음
2. ROWID를 이용하여 테이블 로우 엑세스
3. 다음 로우를 탐색하여 조건에 만족하면 다시 테이블의 로우를 엑세스하고 그렇지 않으면 스캔 종료(이 경우, 한 번의 랜덤, 2개의 인덱스 로우 스캔)
\* 결론: 인덱스의 모든 컬럼을 Equal 연산으로 처리한 경우 처리량에는 차이가 없음. 단지 Equal 연산만 사용했으므로 필요한 데이터만 읽을 수 있게 되었기 때문.
나) 이퀄(=)이 결합순서에 미치는 영향
\- 실제 실무에서는 Equal 연산만으로 처리되지 않고, 다양한 범위 처리 연산(LIKE, BETWEEN, < > 등)이 사용됨
인덱스의 선행 컬럼에 대해 Equal 연산이냐 아니냐는 처리 범위에 크게 영향을 미침
ex) 구성
\- 테이블: TAB1, 컬럼: COL1, COL2
\- 컬럼 COL1: 분포도가 좋지 않음
\- 컬럼 COL2: 분포도가 아주 좋음
\- 사용된 SQL
SELECT * FROM TAB1 WHERE col1='A' AND col2 between '113' and '115' |
---|
\- 엑세스 방식에 따른 구분
\* 분포도가 좋지 않은 COL1 컬럼을 복합인덱스의 선행 컬럼으로 설정한 경우 처리 과정(왼쪽 그림)
1. col1='A' AND col2=113 인 첫번째 로우를 찾는다.
2. ROWID를 이용하여 테이블의 로우를 엑세스 한다.
3. 다음 로우를 차례로 스캔하면서 col1 \!='A' OR col2 > 115 일 때 까지 테이블의 로우를 엑세스 하고 그렇지 않으면 처리를 종료한다.
◎ 이 경우, 첫 번째 컬럼이 Equal 연산이고 두 번째 컬럼에는 정렬이 되어 있으므로 between이 들어갔다 하더라도 필요한 엑세스만 하게 된다.
\* 분포도가 좋은 COL2 컬럼을 복합인덱스의 선행 컬럼으로 설정한 경우 처리 과정(오른쪽 그림)
1. col2=113 AND col1='A' 인 첫번째 로우를 찾는다.
2. ROWID를 이용하여 테이블의 로우를 엑세스 한다.
3. col2 > 115 일 때 까지 계속해서 스캔한다. 스캔한 로우는 col1='A' 인지 체크하여 성공하면 ROWID를 이용하여 테이블을 엑세스한다.
4. col2 > 115 이면 처리를 종료한다.
◎ 이 경우, col2 에 대해서는 체크만 하는 이유는 이 컬럼에 대해서는 정렬이 되어있지 않기 때문. 체크로 인해 ROWID를 이용한 테이블 엑세스만 줄어들 뿐이다.
아래의 경우에서 col1 컬럼에 대해서만 인덱스가 있는 경우에서 차이를 볼 수 있다.
11번의 ROWID 스캔에서 3번의 ROWID 스캔으로만 줄고, 실제 인덱스 내부 처리 범위내에서는 효율을 보지 못했다.
\- 결론: 인덱스의 처리 범위를 고려해야 하며 컬럼의 분포도를 고려한 인덱스 컬럼 선정 방식은 맞지 않다.
조회 시 사용되는 컬럼에 Equal를 많이 쓰는 경우 처리 범위를 크게 줄여주므로 해당 컬럼을 인덱스 선행 컬럼으로 두는 것이 올바르다.
다) IN 연산자를 이용한 징검다리 효과
\- 적용 예
1. 비효율적인 인덱스가 이미 큰 테이블에 대해 사용하고 있는 경우
( 위 예에서 col2, col1 순으로 생성된 결합 인덱스)
2. 올바르게 컬럼 순서로 생성된 인덱스의 쿼리 유형에 상반되는 컬럼 순서의 쿼리가 가끔 사용되는 경우
( col1(=), col2(between) 순서로 들어오는 쿼리가 대부분이고 인덱스고 그 순서로 생성되어있는데, 가끔 col2(=), col1(between) 과 같은 쿼리도 수행되는 경우)
◎ 이 경우, 새롭게 인덱스를 생성하는 것은 인덱스의 중복 투자이므로 올바르지 않음. IN 연산자를 통해 기존 인덱스 만으로도 효율적인 결과를 가져올 수 있음
\- 사용 예제
\* 기존의 between 연산을 이용한 경우(왼쪽 그림)
col2 컬럼 기준으로 넒은 범위 스캔을 한다. col1 컬럼의 필요한 컬럼인 'A'만을 뽑아내지 못한다. 이 경우, 범위에 해당되는 모든 인덱스를 읽으므로 선을 그은것과 같다 하여 '선(line)연산' 이라고도 한다.
\* IN 연산을 이용한 경우(오른쪽 그림)
INLIST ITERATOR(111,112)
1. col2=:value AND col1='A' 인 첫번째 로우를 찾는다. 이 때, 인덱스 트리를 탐색하는 과정이 포함된다.
2. ROWID 탐색을 하고 테이블의 데이터를 뽑아낸다.
3. col2\!=:value OR col1\!='A' 일 때 까지 테이블의 로우를 엑세스하고 만족하지 않으면 다음의 Iterator 값을 뽑아내 1번 과정을 반복한다.
만약 값이 없으면 전체적인 탐색을 종료한다.
◎ 위 연산은 IN이 가지는 연산의 특징으로 col2 , col1 컬럼의 equal 연산을 여러번 수행하여 불필요한 인덱스 값을 읽는 것을 방지한다.
IN 리스트 내부에 있는 데이터들로 인해 필요한 인덱스 값만 읽게 되는데, 해당 인덱스에 점을 찍은것과 같다하여 '점(point)연산' 이라고도 한다.
\* 위 IN 리스트를 사용한 쿼리는 다음의 OR를 사용한 쿼리와 거의 동일한 수행방식을 가진다.
SELECT * FROM TAB1 WHERE (COL2=111 AND COL1='A') OR (COL2=112 AND COL1='A') |
---|
라) 처리 범위에 직접적인 영향을 주지 못하는 컬럼의 추가 기준
\- 결합 인덱스를 사용하는 쿼리 내부에 equal 연산을 사용하지 않는 컬럼 이후로는 범위를 줄여주지 못함.
(예로써, (A,B,C,D)로 구성된 인덱스에 A(=),B(between),C(=),D(=) 와 같이 사용했을지라도 B 연산에서 범위 연산자를 쓰게 됨에 따라 C,D 컬럼은 인덱스 스캔 범위를 줄여주지 못함)
ex) 테이블 구성
\- col1: equal(=) 연산이 사용됨.
\- col2: 사용되지 않거나, 범위 연산자(like, between, < , > 등)이 사용됨.
\- col3: 다양한 처리 연산이 사용됨
\- 사용 쿼리
SELECT * FROM TAB1 WHERE A='2' AND C='61' |
---|
\- 사용 예제
\* (A+B) 컬럼에 복합인덱스를 만든 경우(왼쪽 그림)
1. A 컬럼에 대해서는 인덱스가 존재하므로 A='2' 인 모든 노드를 탐색한다.
2. 그러나 C 컬럼에는 인덱스가 없으므로 ROWID를 통한 테이블 억세스으로 테이블 데이터에서 C 컬럼을 뽑아내 C='61' 것을 필터링 한다.
3. 테이블 내부에서 필터링해서 조건에 맞는 것만 출력한다. 이 과정은 인덱스 스캔이 종료될때 까지 계속 수행된다.
◎ 인덱스 탐색 결과 나온 5건에 대해 모두 ROWID를 통한 테이블 엑세스를 한다. 위 경우 총 5번의 테이블 스캔을 한다.
\* (A+B+C) 컬럼에 복합인덱스를 만든 경우(오른쪽 그림)
1. A 컬럼에 대해서는 인덱스가 존재하므로 A='2' 인 모든 노드를 탐색한다.
2. 앞선 B 컬럼의 검색 조건 부재로 C 컬럼이 인덱스 검색 범위를 줄여주진 못하나, 값은 존재하므로 C='61' 인 것을 필터하는 부분을 인덱스 내부에서 해결한다.
3. 인덱스 내부에서 필터링해서 조건에 맞는 것만 ROWID를 통한 테이블 억세스로 값을 뿌려준다.
◎ 인덱스 탐색 결과 나온 5건에 대해 필터를 통해 걸리진 1건에 대해서만 ROWID를 통한 테이블 스캔을 한다. 위 경우 총 1번의 테이블 스캔을 한다.
< 컬럼 순서를 결정하는 우선 순위 >
[VLDB: 상황 가정 ] : C1, C2, C3, C4 컬럼들에 대해 복합인덱스 컬럼 순서를 어떻게 결정할 것인가?
\- 아래는 쿼리가 사용하고 있는 쿼리 조건의 목록임
1. C1(=), C2(BETWEEN), C3(=)
2. C2(=), C3(BETWEEN), C4(>)
3. C1(=), C2(=), C3(LIKE)
4. C1(=), C2(=), C4(>)
▶ 1단계: 항상 사용하는가?
\* 컬럼의 사용 횟수: C2(4회), C1(3회), C3(2회), C4(2회)
\- 추가 가정: C1 컬럼의 카디널러티가 낮아서 해당 컬럼이 없는 경우에 대해서는 Index Skip Scan 사용하도록 가정
두 번째 경우에서 C2의 경우에서 Equal 연산이 사용되므로 C1 케이스에서 \+1 해서 총 4회 사용으로 가정
- 최종 후보: C1, C2
▶ 2단계: 항상 '='로 사용하는가?
* Equal 연산 사용 횟수: C1(3회), C2(3회)
- C2 컬럼의 경우 첫 번째 경우에서 Between 연산을 사용하므로
- 결과: C1+C2
▶ 3단계: 어느 것이 더 좋은 분포도를 가지는가?
\* 분포도를 고려 및 사용 가능한 추가 연산 형태(예로써 Between을 IN으로 변환 가능한 경우) C1, C2 컬럼의 순서를 추가 검토
\* 이후에 발생할 상황 추이 후 검토
- 엑세스 형태를 수집할 때 현재 사용하고 있는 인덱스 뿐만 아니라 앞으로 등장할 것으로 예측되는 엑세스 형태도 같이 추출
\- 앞으로 어떤 컬럼이 Equal 연산 외에 다른 연산을 사용할 지 검토
▶ 4단계: 자주 정렬되는 순서는 무엇인가?
\* 정렬을 요구하는 구문의 사용 시, 정렬 순서 조사 후 검토. 정렬 시 앞단에 나오는 컬럼을 우선적으로 두는 것이 맞다.
▶ 5단계: 부가적으로 추가시킬 컬럼은 어떤 것으로 할 것인가?
* C3, C4 컬럼의 경우 모든 조건들이 처리 범위를 줄여주는 형태는 아님. C3, C4간의 우선 순위 선정 필요
1. C1(=), C2(BETWEEN), C3(=) : C3이 C4 앞에 오는 것이 유리
2. C2(=), C3(BETWEEN), C4(>): 순서 관계 없음
3. C1(=), C2(=), C3(LIKE): C3이 C4앞에 오는 것이 유리
4. C1(=), C2(=), C4(>): C4가 C3 앞에 오는 것이 유리
- 위의 단순한 결과대로라면 (C1+C2+C3+C4)의 순으로 인덱스 생성하는 것이 좋음
- 그러나, 4 의 경우가 빈번하게 엑세스 되는 경우라면 (C1+C2+C4) 인덱스를 추가 생성해야할 필요도 있을 것임
- 이 이외에 C3, C4 컬럼의 분포도 또한 고려해서 전체적인 상황을 살펴 결정하는 것이 필요
가) 테이블의 엑세스 형태를 최대한 수집
▶ 개발 단계에서의 엑세스 형태 수집
1) 반복 수행되는 엑세스 형태를 찾는다. 반복 수행되는 엑세스는 자신의 수행속도에 반복횟수를 곱한 만큼의 부하를 가져오므로 수행속도에 미치는 영향이 아주 크다.
- Nested Loop Join, Sub-Query, Fetch후 루프 내에서 반복되는 쿼리
2) 분포도가 아주 양호한 컬럼들을 발췌하여 엑세스 유형을 조사한다. 테이블에는 아주 양호한 분포도를 가진 컬럼이 있기 마련이다.
- 인덱스 컬럼 순서를 정할 때등 여러 경우에서 크게 활용될수 있음
3) 자주 넒은 조건이 부여되는 경우를 찾는다. 인덱스는 넓은 범위를 처리하게 될 때 많은 부담을 주게 된다.
\- 전체 범위 처리 시, 넓은 범위 처리는 인덱스 사용 때 수행속도가 좋지 않을 수 있음.
- 처리 범위의 최대 크기와 평균 예상 범위, 자주 사용되는 정렬의 순서, 처리 유형을 수집하면 유용한 자료가 될 것임
4) 조건에 자주 사용되는 주요 컬럼들을 추출하여 엑세스 유형을 조사한다. 빈번하게 사용된다는 것은 시스템 전반에 미치는 영향이 그 만큼 크기 때문에 아주 중요하다.
5) 자주 결합되어 사용되는 컬럼들의 조합 및 정렬되는 순서를 조사한다. 업무적인 측면에서 각 컬럼들간의 상호관계를 잘 파악해 보면 특정 컬럼들끼리 자주 조합하여 사용되는 경합형태를 찾을 수 있다.
- 중심이 되는 컬럼을 찾아 이와 연관되는 컬럼들을 검토
6) 역순으로 정렬되어 있는 경우를 찾는다. End User는 조건의 범위는 넓게 부여하면서 최근의 데이터만 보려고 하는 경우가 많음. 빠른 응답까지 요구함
7) 통계 자료 추출을 위한 엑세스 유형을 조사한다. 통계자료를 추출하는 경우는 대개 범위가 넓다.
- 넓은 범위 처리가 대부분 임. 클러스터나 잘 조합된 결합 인덱스 이용하는 것이 좋음.
▶ 운영 단계에서의 엑세스 형태 수집
* 시점: Test 단계 또는 정상 가동 중인 시스템의 인덱스 교정 시점
1) 어플리케이션 소스 코드에서 SQL을 추출하여 분석용 테이블에 보관한다.
\- 기능: 어플리케이션을 분석하여 SQL 문장을 Repository 에 저장. 손쉽게 SQL 문장을 가져올 수 있음.
- 한계: 동적 SQL 과 같은 쿼리는 가져오는데 한계가 있으며, 수행 횟수와 같은 자세한 사항은 알 수 없음
2) SQL-Trace 파일을 파싱하여 SQL 문장 뿐만 아니라 실행계획, 현재 적용되고 있는 인덱스 , 실행 횟수, 처리 범위등의 매우 상세한 정보 획득할 수 있다.
- 기능: 전체 커다란 trace 결과에 대해 하나의 상세한 레포트도 얻을 수 있으며 상용 SW에 따라 Advice 역할 까지 해주기도 함.
- 한계: 시스템에 큰 부하를 줌. 그러므로 trace 기간은 제한 되어야 하며 그 기간내의 분석 밖에 행할수 없음
3) 공유 SQL 영역에서 직접 SQL을 찾아오는 방법은 보다 SQL 수집을 간편하게 한다.
- 기능: 서버에 부담을 덜 주므로 장기간 수집하는 것이 가능하다.
- 단점: trace에 비해 정보가 빈약하다. 어떤 어플리케이션에서 수행되었는지와 같은
장기간 수집을 하더라도 여전히 모든 SQL 구문 수집하는 것이 어렵다.
▶ 수집된 SQL을 테이블 별로 출력하여 엑세스 형태 기록
- 유사한 형태의 SQL들이 정렬되도록 출력
- SQL문장 별로 정렬하는 것 보다 조건절에 사용된 컬럼별로 정렬하는 것이 보다 유리
-
나) 인덱스 대상 컬럼의 선정 및 분포도 조사
1) 인덱스 대상 컬럼 선정 및 분포도 분석
- 인덱스 대상 컬럼 기준
2) 현행 인덱스 존재 시, 조사한 엑세스 형태가 어떤 인덱스를 타는지 조사
- 사용 인덱스 이외에 해당 연산 수행 시 예상되는 범위 또한 조사
다) 특수한 엑세스 형태에 대한 인덱스 선정
\- 반복해서 엑세스 되는 형태(Critical Access Path)를 찾아내는 것이 우선으로 해야할일임
라) 클러스터링 검토
\- 넓은 범위 처리를 자주하는 컬럼에 대해 클러스터링 검토
마) 결합 인덱스 구성 및 순서의 결정
\- 앞서 설명한 결합 인덱스 순서 구성 원리에 따라 선정
바) 시험 생성 및 테스트
사) 수정이 필요한 애플리케이션 조사 및 선정