인덱스란 무엇인가, 데이터베이스 성능이 달라지는 이유
인덱스란 무엇인가는 데이터베이스를 처음 배우거나 성능 문제를 경험했을 때 반드시 한 번쯤 고민하게 되는 주제다. 데이터베이스를 사용하다 보면 어느 순간부터 쿼리 속도가 눈에 띄게 느려지는 시점을 경험하게 된다. 초기에는 데이터 양이 적기 때문에 큰 문제가 없지만, 서비스가 운영되면서 데이터가 쌓이기 시작하면 같은 쿼리라도 점점 더 많은 시간이 소요되는 상황이 발생한다. 특히 목록 조회나 검색 기능에서 응답 속도가 느려지기 시작하면, 그 영향은 사용자 경험까지 직접적으로 이어지게 된다.
이러한 문제를 해결하기 위해 가장 먼저 고려해야 하는 요소가 바로 인덱스(Index)다. 인덱스는 데이터베이스 성능에 직접적인 영향을 주는 핵심 요소이며, 이를 제대로 이해하는 것만으로도 쿼리 성능을 크게 개선할 수 있다.
이번 글에서는 인덱스가 무엇인지부터 시작해, 실제로 왜 성능이 개선되는지, 그리고 어떤 상황에서 어떻게 적용해야 하는지를 정리해 보려고 한다.
인덱스란 무엇인가 (데이터베이스 인덱스 개념)
인덱스를 한 문장으로 정의하면 데이터를 빠르게 찾기 위해 미리 정리해 둔 구조라고 볼 수 있다. 데이터베이스는 테이블에 데이터를 저장하고 필요할 때 원하는 데이터를 찾아 사용하는데, 이 과정에서 인덱스는 검색 속도를 높여주는 역할을 한다.
인덱스를 이해하기 쉬운 예로 책의 목차를 들 수 있다. 특정 내용을 찾기 위해 책 전체를 읽는 것이 아니라, 목차를 통해 원하는 페이지를 빠르게 찾아가는 방식이다. 데이터베이스에서도 동일하게 인덱스를 활용하면 전체 데이터를 훑지 않고도 필요한 데이터를 빠르게 찾을 수 있다.
Full Scan과 Index Scan의 차이
인덱스를 이해하기 위해서는 Full Scan과 Index Scan의 차이를 이해해야 한다. 인덱스가 없는 경우 데이터베이스는 테이블 전체를 탐색하면서 조건에 맞는 데이터를 찾는다. 이 과정은 데이터가 많아질수록 비효율적이다. 반면 인덱스가 존재하는 경우 데이터베이스는 인덱스를 통해 데이터 위치를 먼저 찾고, 필요한 데이터만 접근하게 된다.
예를 들어 다음과 같은 쿼리가 있다고 가정해 보자.
SELECT * FROM users WHERE email = ‘test@example.com’;
이때 email 컬럼에 인덱스가 없다면 users 테이블 전체를 순회해야 한다. 하지만 인덱스가 존재한다면 email 값을 기준으로 빠르게 해당 데이터 위치를 찾을 수 있다. 이 차이는 데이터가 수천 건일 때보다 수백만 건 이상일 때 훨씬 크게 드러난다.
데이터베이스 인덱스가 성능을 개선하는 이유
인덱스가 성능을 개선하는 핵심 이유는 데이터를 찾는 과정에서 불필요한 탐색을 줄여주기 때문이다. 인덱스가 없는 경우에는 원하는 데이터를 찾기 위해 전체 데이터를 하나씩 확인해야 하지만, 인덱스가 있는 경우에는 이미 정리된 정보를 기반으로 필요한 데이터 위치를 바로 찾아갈 수 있다.
이러한 구조 덕분에 데이터 양이 많아질수록 인덱스의 효과는 더욱 크게 나타난다. 특히 수십만 건 이상의 데이터가 쌓인 환경에서는 인덱스 유무에 따라 조회 속도가 크게 차이 날 수 있다.
언제 인덱스를 사용해야 하는가
모든 컬럼에 인덱스를 생성하는 것은 좋은 방법이 아니다. 인덱스는 비용이 존재하기 때문에 필요한 곳에만 적용해야 한다. 일반적으로 WHERE 조건에 자주 사용되는 컬럼, JOIN 조건에 사용되는 컬럼, ORDER BY나 GROUP BY에 사용되는 컬럼에 인덱스를 적용하는 것이 효과적이다.
CREATE INDEX idx_users_email ON users(email);
이처럼 자주 조회되는 컬럼에 인덱스를 적용하면 성능 개선 효과를 얻을 수 있다.
인덱스가 없는 경우와 있는 경우의 차이
인덱스의 효과를 가장 쉽게 이해하는 방법은 실제 동작 방식을 비교해 보는 것이다.
인덱스가 없는 경우 데이터베이스는 조건에 맞는 데이터를 찾기 위해 테이블 전체를 순차적으로 탐색한다. 데이터가 많아질수록 이 과정은 매우 비효율적이며, 응답 시간도 점점 길어진다.
반면 인덱스가 존재하는 경우 데이터베이스는 인덱스를 먼저 탐색해 데이터의 위치를 빠르게 찾고, 필요한 데이터만 조회한다.
이 차이는 데이터 규모가 커질수록 기하급수적으로 커지며, 실제 서비스 환경에서는 체감 성능 차이로 이어진다.
실제 데이터가 1만 건 수준일 때는 체감 차이가 크지 않을 수 있지만, 100만 건 이상으로 증가하면 상황이 완전히 달라진다.
인덱스가 없는 경우 조회 쿼리는 전체 데이터를 스캔해야 하기 때문에 시간이 선형적으로 증가한다.
반면 인덱스가 있는 경우에는 데이터 양이 증가하더라도 탐색 속도가 크게 영향을 받지 않는다.
이로 인해 검색 기능이나 목록 조회에서 체감 속도가 크게 개선된다.
| 데이터 개수 | 인덱스 없음 | 인덱스 있음 |
|---|---|---|
| 1만 건 | 거의 차이 없음 | 거의 차이 없음 |
| 10만 건 | 느려지기 시작 | 빠름 유지 |
| 100만 건 | 눈에 띄게 느림 | 거의 동일 속도 |
실제 서비스에서 인덱스가 중요한 이유
예를 들어 회원 수가 수십만 명 이상인 서비스에서 특정 사용자를 조회해야 하는 상황을 생각해 보자. 인덱스가 없는 경우 데이터베이스는 모든 사용자 데이터를 하나씩 확인해야 한다. 이 과정은 데이터가 많아질수록 시간이 오래 걸리며, 서비스 응답 속도에도 직접적인 영향을 미친다.
반면 user_id나 email과 같은 컬럼에 인덱스가 존재한다면, 데이터베이스는 전체 데이터를 탐색하지 않고도 해당 사용자의 위치를 빠르게 찾아낼 수 있다. 이 차이는 단순한 성능 차이를 넘어, 실제 사용자 경험에 영향을 주는 요소로 작용한다.
특히 트래픽이 많은 서비스에서는 이러한 차이가 누적되어 서버 부하로 이어질 수 있기 때문에, 인덱스 설계는 필수적인 요소라고 볼 수 있다.
복합 인덱스란 무엇인가 (인덱스 설계 핵심)
복합 인덱스는 두 개 이상의 컬럼을 조합해 생성하는 인덱스다. 특정 쿼리 패턴에 맞춰 설계하면 단일 인덱스보다 더 효율적인 성능을 낼 수 있다.
예를 들어 다음과 같이 조회하는 orders라는 이름의 주문 테이블이 있다고 가정해 보자.
SELECT * FROM orders WHERE user_id = 1 AND status = 'completed';
이 쿼리는 user_id와 status 두 개의 컬럼을 동시에 조건으로 사용하고 있다. 이 경우 각각의 컬럼에 단일 인덱스를 생성하는 것보다, 두 컬럼을 함께 묶은 복합 인덱스를 사용하는 것이 더 효과적일 수 있다.
CREATE INDEX idx_orders_user_status ON orders(user_id, status);
이렇게 복합 인덱스를 생성하면, user_id를 기준으로 먼저 데이터를 좁히고 그 안에서 status를 비교하기 때문에 조회 성능이 개선된다.
데이터베이스 인덱스 종류 간단 정리
대부분의 관계형 데이터베이스는 B-Tree 인덱스를 기본으로 사용한다. 일반적인 조회나 범위 검색에서 가장 많이 사용되는 방식이며, 특별한 설정 없이도 기본적으로 적용되는 경우가 많다.
B-Tree 인덱스는 데이터를 정렬된 구조로 관리하기 때문에 특정 값을 찾는 것은 물론이고, 범위를 지정한 검색이나 정렬 작업에서도 효율적으로 동작한다. 그래서 대부분의 조회 쿼리는 이 인덱스를 기반으로 처리된다고 보면 된다.
이 외에도 특정 상황에서 사용되는 인덱스들이 존재한다.
Hash 인덱스는 특정 값이 정확하게 일치하는 경우에 빠르게 데이터를 찾는 데 유리하다. 예를 들어 “id = 1”과 같이 정확한 값을 찾는 경우에는 빠르게 동작하지만, 범위 검색이나 정렬에는 적합하지 않다.
Full-text 인덱스는 문자열 검색에 특화된 인덱스다. 일반적인 LIKE 검색과 달리, 문장이나 단어 단위로 검색을 수행할 수 있어 게시글 검색이나 검색 기능 구현에 많이 사용된다.
정리하면 대부분의 경우에는 B-Tree 인덱스를 기본으로 사용하면 충분하며, 특정한 목적이 있을 때만 Hash나 Full-text 인덱스를 고려하면 된다.
실무에서 인덱스를 확인하는 방법
인덱스를 생성하는 것보다 중요한 것은 실제로 인덱스가 사용되고 있는지를 확인하는 것이다. 이를 위해 데이터베이스에서는 EXPLAIN이라는 기능을 제공하며, 쿼리가 어떤 방식으로 실행되는지를 확인할 수 있다.
예를 들어 다음과 같은 쿼리가 있다고 가정해 보자.
SELECT * FROM users WHERE email = 'test@example.com';
이 쿼리에 대해 아래처럼 EXPLAIN을 실행해 보자.
EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';
결과 예시는 다음과 같이 표기된다. 이 경우는 인덱스를 사용하고 있을 때의 결과이다.
type: ref
key: idx_users_email
rows: 1
Extra: Using index
이 결과에서 중요한 부분은 key와 type이다.
key는 어떤 인덱스를 사용했는지를 의미한다. 위 결과에서는 idx_users_email 인덱스를 사용했다는 것을 확인할 수 있다.
type은 조회 방식의 효율성을 나타내는 값이다. ref는 비교적 효율적인 조회 방식이며, 인덱스를 활용하고 있다는 의미로 볼 수 있다.
반대로 인덱스가 없는 경우에는 다음과 같은 결과가 나타날 수 있다.
type: ALL
key: NULL
rows: 100000
Extra: Using where
이 경우 key가 NULL로 표시되며, 인덱스를 사용하지 않았다는 의미다.
type이 ALL인 경우는 Full Scan을 의미하며, 테이블 전체를 탐색하고 있다는 뜻이다. rows 값이 크다면 성능에 영향을 줄 가능성이 높다.
초보 개발자가 자주 하는 인덱스 실수
인덱스를 처음 접하면 성능을 높이기 위해 많은 인덱스를 생성하려는 경우가 있다. 하지만 이는 오히려 성능 저하로 이어질 수 있다. 또한 실제 사용되지 않는 컬럼에 인덱스를 생성하는 경우도 흔하게 발생한다. 인덱스는 쿼리 패턴을 기준으로 설계해야 한다는 점이 중요하다. 이 파트는 예시들 보면서 알아 보도록 하자.
인덱스가 오히려 성능을 떨어뜨리는 경우
인덱스는 무조건 성능을 개선하는 도구가 아니다. 잘못 사용하면 오히려 성능 저하를 유발할 수 있다. 대표적인 예는 모든 컬럼에 인덱스를 생성하는 경우다. 이 경우 조회 성능은 일부 개선될 수 있지만, 데이터 입력이나 수정 시 인덱스 갱신 비용이 증가하면서 전체적인 성능이 떨어질 수 있다.
예를 들어 다음과 같이 users라는 사용자 테이블의 여러 컬럼에 인덱스를 생성했다고 가정해 보자.
CREATE INDEX idx_users_name ON users(name);
CREATE INDEX idx_users_age ON users(age);
CREATE INDEX idx_users_city ON users(city);
이 상태에서 아래와 같이 데이터를 삽입하는 경우, 단순히 테이블에 데이터를 추가하는 것이 아니라 모든 인덱스도 함께 갱신되어야 한다.
INSERT INTO users(name, age, city) VALUES('홍길동', 30, '서울');
INSERT 등의 작업 시 인덱스가 많을수록 더 많은 비용이 발생하게 되며, 결국 쓰기 성능이 점점 느려지는 결과로 이어질 수 있다.
또한 LIKE ’%keyword%’와 같이 앞부분이 고정되지 않은 검색은 인덱스를 사용할 수 없다.
SELECT * FROM users WHERE name LIKE '%길동%';
위와 같은 경우 데이터베이스는 문자열의 시작 위치를 알 수 없기 때문에, 인덱스를 활용하지 못하고 전체 데이터를 탐색하게 된다. 즉 Full Scan이 발생한다.
반면 다음과 같은 경우는 인덱스를 사용할 수 있다.
SELECT * FROM users WHERE name LIKE '홍%';
이 경우는 문자열의 시작이 고정되어 있기 때문에 인덱스를 활용한 검색이 가능하다. 이거 은근히 모르는 사람이 많다. 실제 like 검색은 인덱스를 사용할 수 없다고만 기억을 하기도 한다. 하지만 전부 그런건 아니니 꼭 기억해 두자.
인덱스가 적용되지 않는 대표적인 경우
인덱스를 생성했음에도 불구하고 실제 쿼리에서 사용되지 않는 경우도 존재한다. 대표적으로 컬럼에 함수를 적용하는 경우나, 컬럼 타입이 일치하지 않는 경우가 있다. 이러한 상황은 초보 개발자가 자주 겪는 문제 중 하나다.
컬럼에 함수를 사용하는 경우
예를 들어 users라는 사용자 테이블의 email 컬럼에 인덱스가 존재한다고 가정해 보자.
CREATE INDEX idx_users_email ON users(email);
이 상태에서 다음과 같은 쿼리를 실행하면 인덱스가 정상적으로 사용된다.
SELECT * FROM users WHERE email = 'test@example.com';
하지만 컬럼에 함수를 적용하면 상황이 달라진다.
SELECT * FROM users WHERE LOWER(email) = 'test@example.com';
이 경우 데이터베이스는 기존 인덱스를 사용할 수 없다.
이유는 인덱스가 email 값 자체를 기준으로 만들어졌기 때문에, LOWER(email)처럼 변형된 값에는 적용할 수 없기 때문이다.
컬럼 타입이 일치하지 않는 경우
다음과 같이 user_id 컬럼이 숫자 타입이라고 가정해 보자.
SELECT * FROM users WHERE user_id = 1;
이 경우 인덱스를 정상적으로 사용할 수 있다. 하지만 아래와 같이 문자열로 비교하면 문제가 발생할 수 있다.
SELECT * FROM users WHERE user_id = '1';
데이터베이스는 내부적으로 타입 변환을 수행하게 되는데, 이 과정에서 인덱스를 사용하지 못하는 경우가 발생할 수 있다.
계산이나 연산이 포함된 경우
SELECT * FROM orders WHERE price * 2 > 100;
이처럼 컬럼에 연산이 포함되면, 기존 인덱스를 그대로 사용할 수 없기 때문에 Full Scan이 발생할 수 있다.
실제 사용되지 않는 컬럼에 인덱스를 생성하는 경우
아래와 같은 users 테이블에 age라는 키 값에 인덱스를 생성했다.
CREATE INDEX idx_users_age ON users(age);
하지만 실제 쿼리는 다음과 같다고 가정해 보자.
SELECT * FROM users
WHERE email = 'test@example.com';
결국 age 인덱스는 전혀 사용되지 않고 리소스만 소비하게 된다. 인덱스는 단순히 많이 생성하는 것이 중요한 것이 아니라, 실제 쿼리에서 사용되는 컬럼을 기준으로 필요한 위치에 정확하게 설계하는 것이 중요하다.
인덱스를 제대로 사용하는 기준
인덱스를 효과적으로 사용하기 위해서는 몇 가지 기준을 함께 고려해야 한다. 단순히 성능을 높이기 위해 인덱스를 생성하는 것이 아니라, 실제 사용 패턴과 데이터의 특성을 기반으로 판단하는 것이 중요하다.
우선 가장 기본적인 기준은 조회가 자주 발생하는 컬럼인지 여부다. 인덱스는 읽기 성능을 개선하기 위한 구조이기 때문에, 자주 조회되지 않는 컬럼에 인덱스를 생성하는 것은 큰 의미가 없다. 반대로 WHERE 조건에 반복적으로 사용되는 컬럼이라면 인덱스를 적용했을 때 성능 개선 효과를 체감하기 쉽다.
또한 해당 컬럼이 조건절에 어떻게 사용되는지도 중요하다. 단순 비교나 범위 조건으로 자주 사용되는 컬럼은 인덱스 활용도가 높은 반면, 함수나 연산이 자주 적용되는 컬럼은 인덱스를 제대로 활용하지 못하는 경우가 많다. 따라서 쿼리 형태까지 함께 고려해야 한다.
데이터 변경 빈도 역시 중요한 요소다. 인덱스는 데이터가 추가되거나 수정될 때마다 함께 갱신되기 때문에, 변경이 자주 발생하는 컬럼에 인덱스를 생성하면 오히려 전체 성능이 저하될 수 있다. 읽기 성능과 쓰기 성능 사이의 균형을 함께 고려하는 것이 필요하다.
마지막으로 가장 중요한 기준은 실제 쿼리에서 사용되는지 여부다. 인덱스는 존재하는 것만으로 의미가 있는 것이 아니라, 실제 실행 계획에서 사용되어야 효과를 발휘한다. 따라서 인덱스를 생성한 이후에는 EXPLAIN을 통해 쿼리 실행 계획을 확인하고, 인덱스가 제대로 활용되고 있는지를 점검하는 과정이 필요하다.
결국 인덱스 설계는 단순한 기술 적용이 아니라, 데이터 구조와 쿼리 패턴, 그리고 사용 환경을 함께 고려하는 과정이라고 볼 수 있다. 이러한 기준을 바탕으로 인덱스를 설계하면 불필요한 비용을 줄이면서도 성능을 안정적으로 개선할 수 있다. 하지만 사실 그보다 더 중요한 것은 처음 데이터베이스를 어떻게 설계하느냐다. 설계가 잘못된 상태에서는 인덱스를 추가해도 근본적인 문제를 해결하기 어렵기 때문이다. 특히 이 글을 보고 있는 초보 개발자라면 설계를 어떻게 시작해야 하는지가 더 큰 고민이 될 수 있다.
이와 관련해서는 초보 개발자를 위한 데이터베이스 설계 방법 글을 함께 참고하면 이해에 도움이 된다.

결론
인덱스는 데이터베이스 성능을 좌우하는 핵심 요소다. 데이터 양이 증가할수록 그 중요성은 더욱 커지며, 적절하게 활용하면 쿼리 성능을 체감할 수 있을 정도로 크게 개선할 수 있다. 특히 데이터가 수십만 건 이상으로 늘어나는 환경에서는 인덱스의 유무에 따라 조회 속도와 서비스 응답 시간이 크게 달라질 수 있다.
다만 인덱스는 단순히 많이 생성한다고 해서 성능이 좋아지는 구조가 아니다. 잘못된 인덱스 설계는 오히려 쓰기 성능 저하나 불필요한 리소스 사용으로 이어질 수 있으며, 실제 쿼리에서 사용되지 않는 인덱스는 아무런 의미를 가지지 못한다. 따라서 인덱스는 “추가하면 무조건 좋다”는 개념이 아니라, 데이터 구조와 쿼리 패턴을 이해한 상태에서 필요한 곳에 정확하게 적용해야 하는 요소라고 보는 것이 현실적인 접근이다.
또한 인덱스는 한 번 설정하고 끝나는 것이 아니라, 서비스가 성장하면서 지속적으로 점검하고 개선해야 하는 대상이기도 하다. 데이터 양이 증가하거나 쿼리 패턴이 변경되면 기존 인덱스가 비효율적으로 변할 수 있기 때문에, 실행 계획을 확인하고 필요에 따라 재설계하는 과정이 필요하다.
결국 인덱스를 잘 활용한다는 것은 단순히 성능을 개선하는 것을 넘어, 데이터베이스를 효율적으로 설계하고 운영할 수 있는 기반을 마련하는 과정이라고 볼 수 있다. 데이터베이스를 사용하는 이상 인덱스는 반드시 이해해야 하는 개념이며, 이를 제대로 활용하는 것이 곧 성능을 다루는 첫걸음이 된다고 할 수 있겠다. 오늘의 글이 도움이 되었길 바란다.
Add your first comment to this post