인덱스가 빠른 이유는 데이터베이스가 검색할 때 테이블의 모든 데이터를 처음부터 끝까지 확인하지 않고, 별도로 구성된 검색 구조를 이용하여 필요한 데이터의 위치를 빠르게 찾을 수 있기 때문입니다. 특히 관계형 데이터베이스에서 널리 사용되는 B-Tree 계열 인덱스는 데이터를 효율적인 구조로 정리하여 검색해야 하는 범위를 크게 줄입니다. 따라서 데이터의 양이 많아질수록 인덱스를 적절하게 사용하는 것이 중요합니다.

인덱스가 없는 경우의 데이터 검색

먼저 인덱스가 없는 테이블에서 데이터를 검색하는 상황을 생각해볼 수 있습니다. 다음과 같은 users 테이블에 많은 회원 정보가 저장되어 있다고 가정합니다.

SELECT *
FROM users
WHERE user_id = 900000;

user_id에 인덱스가 없다면 데이터베이스는 조건에 맞는 데이터를 찾기 위해 테이블의 여러 행을 확인해야 합니다. 데이터베이스의 실제 실행 방식과 저장 구조에 따라 세부적인 동작은 달라질 수 있지만, 일반적인 전체 테이블 검색에서는 많은 데이터를 확인해야 합니다.

이를 단순하게 표현하면 다음과 같습니다.

1번 → 확인
2번 → 확인
3번 → 확인
4번 → 확인
...
899,999번 → 확인
900,000번 → 발견

데이터가 수십 건 정도라면 이러한 검색 방식도 큰 문제가 되지 않습니다. 하지만 데이터가 수백만 건, 수천만 건으로 증가하면 확인해야 하는 데이터의 양도 크게 증가할 수 있습니다.

이러한 방식은 일반적으로 Full Table Scan이라고 합니다. 테이블 전체 또는 상당 부분을 읽으면서 조건에 맞는 데이터를 찾는 방식입니다.

반면 인덱스가 있다면 데이터베이스는 테이블 자체를 처음부터 확인하는 대신 인덱스를 먼저 확인할 수 있습니다.

B-Tree 구조와 검색 범위 감소

데이터베이스 인덱스에서 중요한 역할을 하는 구조 중 하나가 B-Tree 계열입니다. MySQL의 InnoDB에서도 B+Tree 기반의 인덱스가 사용됩니다.

B-Tree 계열 구조에서는 데이터를 하나의 긴 목록으로 관리하는 것이 아니라 여러 단계의 노드로 나누어 관리합니다. 따라서 원하는 값을 찾을 때 모든 데이터를 순서대로 확인할 필요가 없습니다.

예를 들어 다음과 같이 숫자가 정렬되어 있다고 가정합니다.

10  20  30  40  50  60  70  80  90  100

여기서 90을 찾기 위해 10부터 차례대로 확인한다면 많은 값을 확인해야 합니다. 하지만 정렬된 구조를 활용하면 중간 지점을 기준으로 검색 범위를 줄여가면서 원하는 값을 찾을 수 있습니다.

전체 범위
10 ~ 100

        ↓

50보다 큰 범위
60 ~ 100

        ↓

80보다 큰 범위
90 ~ 100

        ↓

90 발견

실제 B-Tree의 동작은 이보다 훨씬 복잡하지만 핵심적인 원리는 검색 범위를 단계적으로 좁혀가는 것입니다.

데이터가 많아져도 트리의 높이가 데이터 개수만큼 그대로 증가하는 것이 아니라 제한된 단계로 유지되기 때문에 많은 데이터를 비교적 적은 탐색 단계로 찾을 수 있습니다. 이것이 인덱스가 대량의 데이터를 검색할 때 효과적인 주요 이유입니다.

또한 B-Tree 계열 인덱스는 정렬된 구조를 가지고 있기 때문에 특정 값을 찾는 것뿐만 아니라 범위 검색에서도 활용할 수 있습니다.

예를 들어 다음과 같은 SQL을 사용할 수 있습니다.

SELECT *
FROM users
WHERE user_id BETWEEN 100000 AND 200000;

이와 같은 범위 조건은 인덱스가 정렬된 구조를 활용할 수 있는 대표적인 상황입니다.

따라서 인덱스는 단순히 데이터를 복사해 놓은 목록이 아니라, 데이터베이스가 원하는 데이터를 효율적으로 찾을 수 있도록 구성된 별도의 검색 구조라고 이해하는 것이 좋습니다.

인덱스 검색과 실제 데이터 조회

인덱스 검색이 빠르다고 해서 인덱스에 모든 데이터가 들어 있는 것은 아닙니다. 일반적인 인덱스는 검색 대상 컬럼의 값을 바탕으로 정렬된 구조를 만들고, 실제 데이터를 찾을 수 있도록 필요한 정보를 관리합니다.

예를 들어 다음과 같은 테이블이 있다고 가정합니다.

users

user_id | name   | email
--------|--------|----------------
1001    | Kim    | kim@example.com
1002    | Lee    | lee@example.com
1003    | Park   | park@example.com

user_id에 인덱스가 있다면 데이터베이스는 먼저 인덱스에서 1002라는 값을 찾습니다. 이후 인덱스가 가리키는 실제 데이터 위치를 이용하여 테이블의 데이터를 가져올 수 있습니다.

개념적으로 보면 다음과 같습니다.

SQL 조건
   ↓
인덱스 검색
   ↓
조건에 맞는 인덱스 값 발견
   ↓
실제 데이터 위치 확인
   ↓
테이블 데이터 조회

이 과정에서 인덱스는 전체 테이블을 직접 탐색하는 것보다 훨씬 적은 범위에서 원하는 데이터를 찾을 수 있도록 도와줍니다.

다만 실제 동작은 데이터베이스 종류와 스토리지 엔진, 인덱스 종류에 따라 차이가 있습니다. 특히 MySQL InnoDB의 경우 기본 키 인덱스와 보조 인덱스의 구조가 서로 연결되어 있으므로 실제 데이터 접근 과정은 인덱스 종류에 따라 달라질 수 있습니다.

인덱스가 항상 빠른 것은 아닌 이유

인덱스가 검색 속도를 높여준다고 해서 모든 SQL이 인덱스를 사용하면 무조건 빨라지는 것은 아닙니다.

데이터베이스는 SQL을 실행할 때 테이블의 데이터 규모와 인덱스의 상태, 조건의 선택도, 예상되는 읽기 비용 등을 고려하여 어떤 방식으로 데이터를 조회할지 결정합니다.

예를 들어 다음과 같은 컬럼을 생각할 수 있습니다.

gender

M
M
M
F
M
F
M
M
F
...

성별처럼 값의 종류가 매우 적은 컬럼에 인덱스를 생성하면 검색 대상이 충분히 줄어들지 않을 수 있습니다. 이런 경우 데이터베이스가 인덱스를 사용하는 것보다 테이블을 직접 읽는 것이 더 효율적이라고 판단할 수도 있습니다.

또한 다음과 같이 조건을 작성했다고 해서 항상 인덱스를 효과적으로 활용할 수 있는 것은 아닙니다.

SELECT *
FROM users
WHERE name LIKE '%kim%';

문자열의 앞부분에 와일드카드가 사용되는 조건은 일반적인 B-Tree 인덱스가 원하는 방식으로 활용되기 어려울 수 있습니다. 따라서 인덱스를 생성할 때는 단순히 검색에 사용되는 컬럼이라는 이유만으로 결정하기보다 실제 SQL과 데이터 분포를 함께 확인해야 합니다.

MySQL에서는 EXPLAIN을 사용하여 SQL의 실행 계획을 확인할 수 있습니다.

EXPLAIN
SELECT *
FROM users
WHERE user_id = 900000;

실행 계획을 확인하면 데이터베이스가 어떤 인덱스를 선택했는지, 예상되는 접근 방식은 무엇인지 등의 정보를 확인할 수 있습니다.

결국 인덱스의 핵심은 검색해야 할 데이터의 범위를 줄이는 것입니다. 인덱스가 없는 경우 많은 데이터를 직접 확인해야 할 수 있지만, 적절하게 설계된 인덱스가 있다면 정렬된 검색 구조를 이용하여 원하는 데이터에 훨씬 빠르게 접근할 수 있습니다.

그러나 인덱스는 조회 성능을 높이는 대신 저장 공간을 사용하고 데이터가 변경될 때 인덱스도 관리해야 하는 비용이 발생합니다. 따라서 실제 서비스에서는 데이터의 양과 조회 패턴을 분석하고 실행 계획을 확인하면서 필요한 인덱스를 설계하는 것이 중요합니다.

다음 글에서는 여러 컬럼을 하나의 인덱스로 묶어 사용하는 복합 인덱스를 살펴보겠습니다. 복합 인덱스에서는 단순히 여러 컬럼을 추가하는 것뿐만 아니라 컬럼의 순서가 검색 조건과 성능에 영향을 줄 수 있다는 점이 중요한 특징입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

글쓴이

rjsan46@koreabites.com

관련 게시물

복합 인덱스란 무엇인가

복합 인덱스는 데이터베이스에서 두 개 이상의 컬럼을 하나의 인덱스로 묶어 사용하는 방식입니다. 하나의 컬럼만 검색하는 단일 인덱스와 달리 여러 컬럼을 함께 조건으로...

모두 읽어보기

인덱스란 무엇인가

인덱스는 데이터베이스에서 원하는 데이터를 빠르게 찾기 위해 사용하는 자료 구조입니다. 데이터가 많아질수록 모든 데이터를 하나씩 확인하는 방식은 검색 시간이 증가하기 때문에 인덱스를...

모두 읽어보기

서브쿼리란 무엇인가

서브쿼리는 SQL 문 안에 다른 SQL 문을 포함하여 사용하는 쿼리입니다. 하나의 SQL 문에서 필요한 데이터를 먼저 조회한 다음 그 결과를 바깥쪽 쿼리에서...

모두 읽어보기

INNER JOIN과 OUTER JOIN 차이

INNER JOIN과 OUTER JOIN은 서로 다른 테이블의 데이터를 연결할 때 어떤 데이터를 조회 결과에 포함할 것인지 결정하는 JOIN 방식입니다. INNER JOIN은 두...

모두 읽어보기

JOIN이란 무엇인가

JOIN은 SQL에서 서로 다른 테이블에 저장된 데이터를 연결하여 하나의 조회 결과로 가져올 때 사용하는 구문입니다. 관계형 데이터베이스에서는 하나의 테이블에 모든 데이터를 저장하기보다...

모두 읽어보기

DELETE란 무엇인가

DELETE는 SQL에서 테이블에 저장된 기존 데이터를 삭제할 때 사용하는 구문입니다. INSERT가 새로운 데이터를 추가하고 UPDATE가 기존 데이터를 수정한다면 DELETE는 더 이상 필요하지...

모두 읽어보기