옵티마이저
는 SQL 명령을 수행할 때 최적 경로를 찾아주는 역할을 하는 DBMS의 핵심 엔진을 말한다. SQL 실행 시 옵티마이저
는 여러가지의 실행계획
을 세우고 이 실행계획
들의 예상 비용을 정리해서 제일 효율적인 실행계획
에 따라서 쿼리를 수행한다.
옵티마이저는 실행계획을 세우는 방식에 따라서 크게 규칙 기반 옵티마이저
와 비용 기반 옵티마이저
두 가지로 나뉜다.
규칙 기반 옵티마이저
는 아래 테이블과 같이 실행 속도가 빠른 순으로 규칙들을 세워두고 우선순위가 높은 앞서는 방법을 채택한다. 이 방식은 과거 예상 비용을 계산하는 능력이 그리 좋지 않아서 사용했었던 방식이다.
순위 | 설명 |
---|---|
1 | ROWID를 사용한 단일 행인 경우 |
2 | 클러스터 조인에 의한 단일 행인 경우 |
3 | 유일하거나 기본키(Primary Key)를 가진 해시 클러스터 키에 의한 단일 행인 경우 |
4 | 유일하거나 기본키(Primary Key)에 의한 단일 행인 경우 |
5 | 클러스터 조인인 경우 |
6 | 해시 클러스터 조인인 경우 |
7 | 인덱스 클러스터 키인 경우 |
8 | 복합 칼럼 인덱스인 경우 |
9 | 단일 칼럼 인덱스인 경우 |
10 | 인덱스가 구성된 칼럼에서 제한된 범위를 검색하는 경우 |
11 | 인덱스가 구성된 칼럼에서 무제한 범위를 검색하는 경우 |
12 | 정렬-병합(Sort-Merge) 조인인 경우 |
13 | 인덱스가 구성된 칼럼에서 MAX 혹은 MIN을 구하는 경우 |
14 | 인덱스가 구성된 칼럼에서 ORDER BY를 실행하는 경우 |
15 | 전체 테이블을 스캔(FULL TABLE SCAN)하는 경우 |
위 테이블을 이해하고 있으면 쿼리문을 작성할 때 보다 효율적으로 작성할 수 있다. 하지만 테이블에 row가 적은 경우에는 FULL TABLE SCAN(순위:15)
를 하는게 더 빠를 수 있는데 INDEX
를 사용한다거나 하는 비효율적인 실행계획이 도출될 수도 있다는 단점도 있다.
또 옵티마이저의 실행계획을 유도하는 힌트
나 HASH JOIN
는 규칙 기반 옵티마이저
이후에 나온 개념이므로 여기서는 사용할 수 없다는 단점이 있다.
비용 기반 옵티마이저
는 쿼리를 수행하는데 소요되는 일의 양 혹은 시간을 기준으로 최적화를 수행한다. 다양한 객체 통계정보(테이블, 컬럼, 인덱스)와 시스템 통계정보(CPU, 디스크 I/O 속도 등)를 기반으로 최대 2000 개의 실행계획을 세우고 최소 비용의 실행계획을 수행한다. 통계정보
가 없는 경우에는 비효율적인 실행계획을 수립할 수 있기 때문에 통계정보를 정확하게 유지할 수 있도록 해야한다.
보통 FULL TABLE SCAN 과 INDEX SCAN 을 손 쉽게 유도할 수 있다. 보통 데이터가 적은 테이블에서는 INDEX 를 타면서 시간을 소요할 필요가 없이 FULL TABLE SCAN을 하는게 낫고, 테이블의 크기가 크면 INDEX SCAN을 하는 것이 좋다.
당연한 말이지만 INDEX SCAN이 유리한데도 옵티마이저가 FULL TABLE SCAN을 탄다면 INDEX를 생성하는 것이 좋다. 물론 INDEX를 필요할 때마다 만드는 것은 UPDATE, DELETE 등의 속도를 저해하기 때문에 마냥 좋은 것은 아니다.
ROWID SCAN은 단일 행 접근이 매우 빠르기 때문에 ROWID SCAN이 유리하다고 판단되는 경우는 대부분 ROWID SCAN을 타는 것이 가장 유리하다.
다양한 상황에서 다양한 종류의 INDEX SCAN 이 사용된다.
=
로 명시된 경우인덱스는 별도의 TABLE을 만들어 대상 TABLE 의 데이터를 정렬하고 해당 데이터의 물리적 위치를 VALUE로 갖는다. 대부분의 DB 속도 저하가 SELECT 문 특히 조건 검색 시 발생하기 때문에 가장 먼저 대안으로 INDEX를 생각해볼 수도 있다.
Index Skew는 인덱스 엔트리가 왼쪽 또는 오른쪽에 치우치는 현상을 말한다.
예를 들어, 대량의 delete 작업을 마치고 나면 위 그림처럼 인덱스 왼쪽에 있는 리프 블록들은 텅 비는 반면 오른쪽은 꽉 찬 상태가 된다
Oracle의 경우 텅 빈 인덱스 블록은 커밋하는 순간 freelist로 반환되지만 인덱스 구조 상에는 그대로 남는다. 상위 브랜치에서 해당 리프 블록을 가리키는 엔트리가 그대로 남아 있어 인덱스 정렬 순서상 그 곳에 입력될 새로운 값이 들어오면 언제든 재사용될 수 있다. 문제는 다시 채워질 때까지 인덱스 스캔 효율이 낮다는 데에 있다. SQL Server에선 Index Skew 현상이 발생하지 않는다. 주기적으로 B+Tree 인덱스를 체크함으로써 지워진 레코드와 페이지를 정리해 주는 메커니즘을 갖기 때문이다. 인덱스 레코드를 지우면 표시해뒀다가 별도 쓰레드에 의해 비동기 방식으로 제거되는데, 그 과정에서 텅 빈 페이지가 발견되면 인덱스 구조에서 제거된다.
Index Sparse는 인덱스 블록 전반에 걸쳐 밀도(density)가 떨어지는 현상을 말한다.
지워진 자리에 인덱스 정렬 순서에 따라 새로운 값이 입력되면 그 공간은 재사용되지만 위와 같은 대량의 delete 작업이 있고 난 후 한동안 인덱스 스캔 효율이 낮다는 데에 문제가 생긴다. 왼쪽, 오른쪽, 중간 어디든 Index Skew처럼 블록이 아예 텅 비면 곧바로 freelist로 반환돼 언제든 재사용되지만, Index Sparse는 지워진 자리에 새로운 값이 입력되지 않으면 영영 재사용되지 않을 수도 있다. 총 레코드 건수가 일정한데도 인덱스 공간 사용량이 계속 커지는 것은 대개 이런 현상에 기인한다.
Fragmentation 때문에 인덱스 크기가 계속 증가하고 스캔 효율이 나빠지면 인덱스를 재생성하거나 DBMS가 제공하는 명령어를 이용해 빈 공간을 제거하는 것이 유용할 수 있다. 하지만 일반적으로 인덱스 블록에는 어느 정도 공간을 남겨두는 것이 좋다. 왜냐하면, 빈 공간을 제거해 인덱스 구조를 슬림(slim)화하면 저장 효율이나 스캔 효율엔 좋겠지만 인덱스 분할이 자주 발생해 DML 성능이 나빠질 수 있기 때문이다. 인덱스 분할에 의한 경합을 줄일 목적으로, 초기부터 빈 공간을 남기도록 옵션을 주고 인덱스를 재성성할 수도 있다. 하지만 그 효과는 일시적이다. 언젠가 빈 공간이 다시 채워지기 때문이며, 결국 적당한 시점마다 재생성 작업을 반복하지 않는 한 근본적인 해결책이 되지는 못한다. 인덱스를 재생성하는 데 걸리는 시간과 부하도 무시할 수 없다. 따라서 인덱스의 주기적인 재생성 작업은 아래와 같이 예상효과가 확실할 때만 시행하는 것이 바람직하다.
결합 인덱스란 두 개 이상의 컬럼을 묶어서 인덱스로 만들어진 인덱스를 말한다. AND 조건절에 많이 사용되는 컬럼들을 묶어서 테이블을 생성한다. 결합 인덱스는 컬럼의 순서에 따라서 성능에 많은 영향을 받게 된다. 특히 첫번째 컬럼이 많은 역할을 한다.
=
가 아니라면 후행 컬럼 조건에서 =
를 사용해도 처리범위는 줄어들지 않는다.