본문 바로가기

데이터 사이언스/SQL

[SQLD 학습 자료 요약] SQL 기본 및 활용 3.2. 인덱스 기본

본 문서의 내용은 한국데이터산업진흥원에서 펴낸 SQL 전문가 가이드를 기반으로 자격증 취득에 도움이 될 개념을 정리한 것입니다.

SQL 전문가 가이드
국내도서
저자 : 한국데이터산업진흥원
출판 : 한국데이터산업진흥원 2020.05.29
상세보기

 

2. 인덱스 기본

1. 인덱스 특징과 종류

인덱스는 테이블을 기반으로 선택적으로 생성할 수 있는 구조이다. 테이블에 인덱스를 생성하지 않아도 되고 여러 개를 생성해도 된다. 인덱스의 기본적인 목적은 검색 성능의 최적화이다. 즉, 검색 조건을 만족하는 데이터를 인덱스를 통해 효과적으로 찾을 수 있도록 돕는다. 그렇지만 Insert, Update, Delete 등과 같은 DML 작업은 테이블과 인덱스를 함께 변경해야 하기 때문에 오히려 느려질 수 있다는 단점이 존재한다.

 

 

인덱스는 조회만을 위한 오브젝트이며, 삽입, 삭제, 갱신의 경우 오히려 부하를 가중한다.

 

 

인덱스가 존재하는 상황에서 데이터를 입력하면, 매번 인덱스 정렬이 일어나므로 데이터 마이그레이션 같이 대량의 데이터를 삽입할 때는 모든 인덱스를 제거하고, 데이터 삽입이 끝난 후에 인덱스를 다시 생성하는 것이 좋다.

 

보조 인덱스는 UNIQUE INDEX가 아니라면 중복 데이터의 입력이 가능하다. (기본 인덱스는 중복된 키 값이 나타날 수 없다.)

 

 

인덱스 특징

  • 인덱스를 생성할 때 정렬 순서를 내림차순으로 하면 내림차순 정렬도 가능하다.
  • 규칙기반 옵티마이저는 규칙에 따라 적절한 인덱스가 존재하면 전체 테이블 스캔보다는 항상 인덱스를 사용하려고 한다.
  • 인덱스를 스캔하여 테이블로 데이터를 찾아가는 방식이 랜덤 액세스인데, 이러한 작업은 부하가 크기 때문에 매우 많은 양의 데이터를 읽을 경우에는 테이블 전체 스캔이 유리할 수 있다.
  • 인덱스를 구성하는 컬럼 이외의 데이터를 UPDATE할 때에는 인덱스로 인한 부하가 발생하지 않는다. 즉, 성능이 저하되지 않을 수 있다.

 

 

가. 트리 기반 인덱스

DBMS에서 가장 일반적인 인덱스는 B-트리 인덱스이다.

B-Tree 인덱스 구조 (출처: SQL 전문가 가이드)

 

브랜치 블록 중에서 가장 상위에서 있는 블록을 루트 블록(Root Block)이라고 한다.

 

브랜치 블록은 분기를 목적으로 하는 블록이다. 브랜치 블록은 다음 단계의 블록을 가리키는 포인터를 가지고 있다.

 

리프 블록은 인덱스를 구성하는 칼럼의 데이터와 해당 데이터를 가지고 있는 행의 위치를 가리키는 레코드 식별자(RID, Record Identifier/Rowid)로 구성되어 있다. 리프 블록은 양방향 링크(Double Link)를 가지고 있다. 이것을 통해서 오름 차순(Ascending Order)과 내림 차순(Descending Order) 검색을 쉽게 할 수 있다.

 

B-트리 인덱스는 ‘=’로 검색하는 일치(Exact Match) 검색과 ‘BETWEEN’, ‘>’ 등과 같은 연산자로 검색하는 범위(Range) 검색 모두에 적합한 구조이다. 그리고 일반적으로 테이블 내의 데이터 중 10% 이하의 데이터를 검색할 때 유리하다.

 

B-Tree 인덱스 검색 예시 (출처: SQL 전문가 가이드)

인덱스에서 원하는 값을 찾는 과정

  1. 브랜치 블록 가장 왼쪽값이 찾고자 하는 값보다 작거나 같으면 왼쪽 포인터로 이동
  2. 찾고자 하는 값이 브랜치 블록의 값 사이에 존재하면 가운데 포인터로 이동
  3. 오른쪽에 있는 값보다 크면 오른쪽 포인터로 이동
  4. 위 과정을 리프 블록을 찾을 때까지 반복

 

예를 들어, 37 을 찾고자 한다면 루트 블록에서 50 보다 작으므로 왼쪽 포인터로 이동한다. 37 은 왼쪽 브랜치 블록의 11 과 40 사이의 값이므로 가운데 포인터로 이동한다. 이동한 결과 해당 블록이 리프 블록이므로 37 이 블록 내에 존재하는지 검색한다.

 

인덱스 구성 칼럼은 동일하지만 칼럼의 순서가 다르면 서로 다른 인덱스로 생성할 수 있다. 예를 들어, JOB+SAL 칼럼 순서의 인덱스와 SAL+JOB 칼럼 순서의 인덱스를 별도의 인덱스를 생성할 수 있다. 인덱스의 칼럼 순서는 질의의 성능에 중요한 영향을 미치는 요소이다.

 

 

BITMAP 인덱스

시스템에서 사용될 질의를 시스템 구현 시에 모두 알 수 없는 경우인 DW 및 AD-HOC 질의 환경을 위해서 설계되었으며, 하나의 인덱스 키 엔트리가 많은 행에 대한 포인터를 저장하고 있는 구조이다.

 

 

나. SQL Server 의 클러스터형 인덱스

클러스터형 인덱스의 특징

  1. 인덱스의 리프 페이지가 곧 데이터 페이지이다. 따라서 테이블 탐색에 필요한 레코드 식별자가 리프 페이지에 없다. 클러스터형 인덱스의 리프 페이지를 탐색하면 해당 테이블의 모든 컬럼 값을 얻을 수 있다.
  2. 리프 페이지의 모든 Row는 인덱스 키 컬럼 순으로 물리적으로 정렬되어 저장된다. 테이블 Row는 물리적으로 한 가지 순서로만 정렬될 수 있다. 그러므로 클러스터형 인덱스는 테이블당 한 개만 생성할 수 있다.

 

클러스터형 인덱스 구조 (출처: SQL 전문가 가이드)

SQL Server의 클러스터형 인덱스는 Oracle의 IOT와 매우 유사하다.

 

 


2. 전체 테이블 스캔과 인덱스 스캔

가. 전체 테이블 스캔

전체 테이블 스캔 방식으로 데이터를 검색한다는 것은 테이블에 존재하는 모든 데이터를 읽어 가면서 조건에 맞으면 결과로서 추출하고 조건에 맞지 않으면 버리는 방식으로 검색한다.

 

FTS (Full Table Scan) 방식 (출처: SQL 전문가 가이드)

Oracle 의 경우 위 그림과 같이 검색 조건에 맞는 데이터를 찾기 위해서 테이블의 고수위 마크(HWM, High Water Mark) 아래의 모든 블록을 읽는다. 고수위 마크는 테이블에 데이터가 쓰여졌던 블록 상의 최상위 위치(현재는 지워져서 데이터가 존재하지 않을 수도 있음)를 의미한다. 전체 테이블 스캔 방식으로 데이터를 검색할 때 고수위 마크까지의 블록 내 모든 데이터를 읽어야 하기 때문에 모든 결과를 찾을 때까지 시간이 오래 걸릴 수 있다.

 

 

전체 데이터를 읽는 경우는 인덱스를 사용하지 않는다!

 

 

이렇게 읽은 블록들은 재사용성이 떨어진다. 그래서전체 테이블 스캔 방식으로 읽은 블록들은 메모리에서 곧 제거될 수 있도록 관리된다.

 

 

옵티마이저가 연산으로 전체 테이블 스캔 방식을 선택하는 이유

  1. SQL 문에 조건이 존재하지 않는 경우
  2. SQL 문의 주어진 조건에 사용 가능한 인덱스가 존재하지 않는 경우
  3. 옵티마이저가 대부분의 블록에 액세스 해야 한다고 판단하는 경우
  4. 병렬처리 방식 또는 전체 테이블 스캔 방식의 힌트를 사용한 경우

 

 

나. 인덱스 스캔

검색을 위해 인덱스의 리프 블록을 읽으면 인덱스 구성 칼럼의 값과 테이블의 레코드 식별자를 알 수 있다. 인덱스에 존재하지 않는 칼럼의 값이 필요한 경우에는 현재 읽은 레코드 식별자를 이용하여 테이블을 액세스해야 한다. 그러나 SQL 문에서 필요로 하는 모든 칼럼이 인덱스 구성 칼럼에 포함된 경우 테이블에 대한 액세스는 발생하지 않는다.

 

 

인덱스 스캔 방식

1. 인덱스 유일 스캔

  • 유일 인덱스(Unique Index)를 사용하여 단 하나의 데이터를 추출하는 방식이다. 유일 인덱스는 중복을 허락하지 않는 인덱스이다. 유일 인덱스 구성 칼럼에 모두 '='로 값이 주어지면 결과는 최대 1 건이 된다. 인덱스 유일 스캔은 유일 인덱스 구성 칼럼에 대해 모두 ‘=’로 값이 주어진 경우에만 가능한 인덱스 스캔 방식이다.

2. 인덱스 범위 스캔

  • 인덱스를 이용하여 한 건 이상의 데이터를 추출하는 방식이다. 유일 인덱스의 구성 칼럼 모두에 대해 ‘=’로 값이 주어지지 않은 경우와 비유일 인덱스(Non-Unique Index)를 이용하는 모든 액세스 방식은 인덱스 범위 스캔 방식으로 데이터를 액세스하는 것이다.

3. 인덱스 역순 범위 스캔

  • 인덱스의 리프 블록의 양방향 링크를 이용하여 내림차순으로 데이터를 읽는 방식이다. 이 또한 인덱스 범위 스캔의 일종이다.

 

 

다. 전체 테이블 스캔과 인덱스 스캔 방식의 비교

전체 테이블 스캔 vs. 인덱스 스캔

인덱스 스캔은 인덱스에 존재하는 레코드 식별자를 이용해서 검색하는 데이터의 정확한 위치를 알고서 데이터를 읽는다. 그렇기 때문에 인덱스 스캔 방식에서는 불필요하게 다른 블록을 더 읽을 필요가 없다. 따라서 한번의 I/O 요청에 한 블록씩 데이터를 읽는다.

 

전체 테이블 스캔은 데이터를 읽을 때 한번의 I/O 요청으로 여러 블록을 한꺼번에 읽는다. 어차피 테이블의 모든 데이터를 읽을 것이라면 한 번 읽기 작업을 할 때 여러 블록을 함께 읽는 것이 효율적이다.

 

 


↓SQL 전문가 가이드 요약 목록

더보기

 

따로 PDF 파일이 필요하신 분은 댓글을 통해 메일 주소 적어주시기 바랍니다.