본문 바로가기
전쟁과 기술

정리된 데이터는 어떻게 빨리 찾을까? 데이터베이스 인덱스의 원리

by 전쟁과 기술 2026. 9. 17.

앞선 글에서는 고객, 주문, 상품처럼 성격이 다른 대상을 나누어 저장하는 이유를 살펴보았다. 같은 주소와 상품 정보를 주문마다 반복하지 않고, 각각의 사실을 알맞은 테이블이 맡게 하면 한곳만 고쳐도 데이터가 일관되게 유지된다. 기본키와 외래키가 나뉜 기록의 관계를 이어주므로 필요할 때 다시 조합할 수도 있다.

그런데 잘 나누어 저장했다는 사실만으로 필요한 정보가 곧바로 나타나는 것은 아니다. 고객이 수십 명이고 주문이 몇백 건일 때는 어느 방식으로 찾아도 큰 차이가 없다. 몇 년이 지나 주문이 수백만 건으로 늘고, 장비마다 정비와 장애 이력이 계속 쌓이면 이야기가 달라진다. 정확하게 저장된 자료도 제때 찾지 못하면 실제 업무에서는 쓰기 어렵다.

정규화가 같은 사실을 어디에 저장할 것인지 정하는 과정이었다면, 인덱스는 그렇게 저장된 사실을 어떻게 빨리 찾아갈 것인지 준비하는 장치다. 이번 글에서는 검색 명령의 문법보다 한 단계 앞에서, 데이터베이스가 많은 기록 사이에서 원하는 행을 찾아가는 원리를 살펴보려 한다.

한줄 요약

정규화가 데이터의 중복을 줄여 저장 위치를 분명하게 한다면, 인덱스는 자주 찾는 값과 실제 행의 위치를 따로 정리해 많은 기록을 처음부터 모두 읽지 않고도 목적지에 도달하게 한다.

표를 나누었더니 찾아야 할 곳도 여러 군데가 되었다

정규화하기 전의 주문표에는 고객 이름과 주소, 상품명과 가격이 한 줄에 함께 들어 있었다. 중복은 많았지만 한 행만 열면 주문 내용을 대강 볼 수 있었다. 정규화한 뒤에는 고객 정보는 고객 테이블에, 주문은 주문 테이블에, 상품은 상품 테이블에 나뉜다. 주문 한 건을 완성하려면 주문번호와 고객번호, 상품번호를 따라 관련 기록을 찾아야 한다.

이 구조가 잘못된 것은 아니다. 오히려 각각의 사실을 한곳에서 책임지게 했기 때문에 주소를 바꾸거나 상품정보를 수정할 때 값이 서로 어긋날 가능성이 줄어든다. 다만 데이터를 꺼내는 과정에서는 어떤 고객인지, 어느 주문인지, 어떤 상품인지 정확한 기록을 찾아 연결해야 한다.

기록이 적을 때에는 데이터베이스가 테이블의 첫 행부터 차례로 읽어도 금방 끝난다. 직원 열 명 가운데 한 명을 찾거나 오늘 들어온 주문 스무 건을 확인하는 데 특별한 길잡이는 필요하지 않다. 하지만 주문 테이블이 수백만 행이고 고객 한 명의 주문 다섯 건만 필요하다면 처음부터 끝까지 읽는 방식은 낭비가 커진다.

이처럼 모든 행을 차례로 확인하는 방식을 순차 스캔 또는 전체 테이블 스캔이라고 한다. 단순하고 확실하지만 자료가 커질수록 읽어야 할 양도 함께 늘어난다. 정규화가 정확성의 문제를 해결한 뒤, 데이터의 규모가 검색 속도라는 새로운 문제를 드러내는 지점이다.

많은 종이 기록과 디지털 자료 사이에서 필요한 한 건을 처음부터 차례로 찾는 업무 장면

두꺼운 책에서 ‘패킷’이라는 용어가 설명된 곳을 찾는다고 해보자. 첫 장부터 읽으면 언젠가는 발견하겠지만, 보통은 책 뒤의 찾아보기를 펼친다. 용어가 일정한 순서로 정리되어 있고 옆에는 해당 페이지가 표시되어 있어 본문 전체를 읽지 않고도 목적지 근처로 갈 수 있다.

데이터베이스 인덱스도 이와 비슷한 역할을 한다. 원본 테이블을 통째로 하나 더 만드는 것이 아니라, 자주 찾는 열의 값과 실제 행을 찾아갈 수 있는 정보를 별도로 정리한다. 고객번호에 인덱스가 있다면 데이터베이스는 고객번호가 정리된 구조에서 원하는 값을 먼저 찾고, 그 위치를 따라 고객의 실제 기록으로 이동한다.

앞선 글에서 기본키는 한 행을 다른 행과 구별하는 이름표라고 설명하였다. 기본키가 누구인지를 정확하게 가리킨다면, 인덱스는 그 이름표가 붙은 행이 어디에 있는지를 빨리 찾아가는 길에 가깝다. 둘은 함께 쓰이는 경우가 많지만 같은 개념은 아니다. 기본키의 중심은 식별과 중복 방지이고, 인덱스의 중심은 탐색 경로다.

책과 다른 점도 있다. 인쇄된 책의 내용은 거의 바뀌지 않지만 데이터베이스에는 새 주문이 들어오고 고객 정보가 수정되며 오래된 기록이 삭제된다. 원본이 바뀔 때마다 인덱스도 함께 고쳐져야 한다. 길잡이가 가리키는 위치와 실제 자료가 어긋나면 빠른 검색보다 더 큰 문제가 생기기 때문이다.

B-Tree는 한 번에 전부 보지 않고 범위를 줄여 간다

관계형 데이터베이스에서 가장 흔하게 쓰이는 인덱스 구조 가운데 하나가 B-Tree다. 이름만 들으면 복잡한 나무 모양을 외워야 할 것 같지만, 기본 생각은 어렵지 않다. 값을 일정한 순서로 나누어 두고, 찾는 값이 어느 범위에 있는지 단계마다 판단한다.

장비 식별번호 하나를 찾는 상황을 떠올려 보자. 모든 번호를 첫 번째부터 비교하지 않고, 첫 단계에서 작은 번호 구역과 큰 번호 구역 가운데 한쪽을 고른다. 다음 단계에서 범위를 다시 나누고, 몇 차례 선택을 거쳐 목표값이 있는 곳에 도착한다. 서류함 전체를 뒤지는 대신 분류표를 보고 건물, 층, 선반, 칸의 순서로 목적지를 좁혀 가는 모습과 닮았다.

값이 정렬되어 있으므로 하나의 번호뿐 아니라 일정한 범위를 찾을 때도 유리하다. 특정 기간에 발생한 장애 이력, 일정 금액 이상인 주문, 어느 시점 이후 등록된 장비처럼 앞뒤 순서가 있는 조건을 빠르게 좁힐 수 있다. 결과를 날짜순으로 보여줄 때 이미 정리된 순서를 활용하는 경우도 있다.

모든 검색에 같은 구조가 쓰이는 것은 아니다. 정확히 같은 값만 찾는 경우, 문서 안의 낱말을 찾는 경우, 가까운 위치를 찾는 경우에는 다른 방식이 더 알맞을 수 있다. 인덱스는 만능 지름길이 아니라 자주 던지는 질문의 모양에 맞추어 미리 마련한 경로라고 보는 편이 정확하다.

정돈된 자료 구조를 따라 검색 범위를 단계적으로 좁혀 필요한 기록에 도달하는 장면

기본키가 있다고 모든 검색이 빨라지는 것은 아니다

주문번호처럼 각 행을 구별하는 값은 기본키로 관리되고 대개 빠르게 찾을 수 있는 구조도 함께 마련된다. 그러나 사용자가 언제나 주문번호를 알고 검색하는 것은 아니다. 고객번호로 주문을 찾기도 하고, 주문일이나 배송상태를 기준으로 목록을 확인하기도 한다.

어떤 열에 인덱스를 둘지는 실제로 자주 찾는 조건을 살펴 결정해야 한다. 고객별 주문내역을 매일 확인한다면 고객번호가 후보가 되고, 날짜별 장애 기록을 반복해 조회한다면 발생 시각을 검토할 수 있다. 서로 다른 테이블의 관계를 잇는 외래키도 자주 검색되는 값이라면 빠른 연결을 위해 별도의 경로가 필요할 수 있다.

두 개 이상의 값을 함께 찾는 일이 많을 때에는 복합 인덱스를 사용하기도 한다. 예를 들어 특정 장비의 최근 정비 이력을 자주 확인한다면 장비 식별번호와 정비일을 함께 고려한 구조가 도움이 될 수 있다. 다만 어느 값을 먼저 놓는지에 따라 쓸 수 있는 검색이 달라진다. 성과 이름 순으로 정리된 명부에서 성을 모른 채 이름만 찾기 어려운 것과 비슷하다.

테이블의 열만 보고 기계적으로 정할 수 없는 이유가 여기에 있다. 같은 데이터라도 쇼핑몰, 장비관리, 의료, 물류에서 자주 묻는 질문은 다르다. 인덱스 설계에는 데이터 구조뿐 아니라 사람이 그 정보를 어떤 순서와 조건으로 사용하는지가 함께 들어간다.

인덱스가 있어도 처음부터 읽는 편이 나을 때가 있다

찾아보기는 책에서 몇 쪽만 찾을 때 유용하다. 책의 절반을 읽어야 한다면 찾아보기에서 페이지를 하나씩 확인하는 것보다 처음부터 이어서 읽는 편이 나을 수 있다. 데이터베이스도 같은 판단을 한다.

수백만 행 가운데 한두 건을 찾는다면 인덱스의 이점이 크다. 반대로 대부분의 행을 가져와야 한다면 인덱스에서 위치를 확인한 뒤 원본으로 여러 번 이동하는 과정이 오히려 부담이 될 수 있다. 값의 종류가 적고 한 값에 많은 행이 몰려 있을 때도 효과가 작아진다. 장비 상태가 대부분 ‘정상’인데 정상인 장비 전체를 요청한다면 결과 자체가 너무 많기 때문이다.

그래서 인덱스를 만들었다고 모든 조회가 자동으로 빨라지는 것은 아니다. 데이터베이스는 테이블의 크기와 값의 분포, 예상되는 결과의 양을 살펴 인덱스를 이용할지 처음부터 읽을지 결정한다. 인덱스가 사용되지 않았다는 사실만으로 오류라고 단정할 수 없는 이유다.

실제 성능을 개선하려면 느리다는 느낌보다 데이터베이스가 어떤 경로로 기록을 읽었는지 확인해야 한다. 검색 조건과 맞지 않는 인덱스일 수도 있고, 필요한 범위가 너무 넓거나 통계가 현재 데이터와 달라졌을 수도 있다. 길을 만드는 일만큼 어느 길이 실제로 선택되었는지 보는 일이 중요하다.

찾는 길이 많아질수록 새 기록을 넣는 일은 무거워진다

인덱스가 검색을 빠르게 해준다면 모든 열에 만들고 싶어질 수 있다. 하지만 새로운 주문이 들어올 때에는 원본 테이블만 바뀌는 것이 아니다. 주문번호, 고객번호, 주문일에 각각 인덱스가 있다면 새 행의 위치를 모든 구조에 반영해야 한다. 값을 수정하거나 행을 삭제할 때도 마찬가지다.

인덱스는 별도의 저장공간을 사용하고 백업해야 할 양도 늘린다. 값이 자주 바뀌거나 짧은 시간에 기록이 대량으로 들어오는 체계에서는 갱신 부담이 눈에 띌 수 있다. 조회 한 번을 빠르게 하려고 매초 반복되는 저장 작업을 무겁게 만들면 전체 흐름은 오히려 나빠진다.

정규화에서도 무조건 표를 많이 나누는 것이 답은 아니었다. 인덱스 역시 많을수록 좋은 것이 아니다. 실제로 자주 사용되는 검색은 무엇인지, 한 번에 돌아오는 결과는 얼마나 되는지, 자료는 얼마나 자주 추가되고 바뀌는지를 함께 살펴야 한다.

결국 데이터베이스 설계에는 균형이 필요하다. 정규화는 정확성을 높이지만 여러 테이블을 찾아 연결하는 과정을 만든다. 인덱스는 그 탐색을 도울 수 있지만 저장과 변경의 비용을 더한다. 어느 한쪽만 최대화하기보다 업무에서 중요한 흐름이 무엇인지 먼저 정해야 한다.

정보체계에서는 정확한 기록과 빠른 접근이 함께 필요하다

장비관리 체계를 예로 들면 장비 기본정보, 정비 이력, 장애 기록과 부품 교체내역은 성격에 따라 나누어 보관하는 편이 안전하다. 같은 장비명과 소속을 여러 표에 반복해서 적는 일을 줄이고, 하나의 식별값으로 관계를 이어야 데이터가 서로 다른 대상을 가리키는 문제를 막을 수 있다.

그러나 장애가 발생한 장비의 최근 정비 이력을 확인할 때 몇 년 치 기록을 매번 처음부터 읽는다면 잘 정리한 구조의 장점이 현장에서 잘 느껴지지 않는다. 장비 식별값과 발생 시각처럼 반복해서 찾는 항목에 알맞은 경로가 있어야 필요한 이력에 빠르게 도달할 수 있다.

C4I처럼 여러 출처의 자료가 계속 들어오는 환경에서는 두 요구가 더 선명해진다. 서로 다른 센서와 체계가 보낸 정보를 정확한 대상으로 연결해야 하고, 사용자가 요구한 시점의 정보를 늦지 않게 찾아 보여주어야 한다. 인덱스는 군사 분야만의 기술이 아니지만 정보의 시의성이 중요한 환경에서는 작은 지연도 운용자가 느끼는 체계의 신뢰도에 영향을 준다.

짧은 생각

정보체계의 화면이 느리면 서버부터 바꾸고 싶어질 때가 있다. 그러나 장비가 부족해서가 아니라 자주 찾는 정보로 가는 길이 준비되지 않아 느린 경우도 있다. 좋은 체계는 모든 자료를 한꺼번에 보여주는 체계보다, 정확하게 저장된 기록을 필요한 순간에 꺼내 쓸 수 있는 체계에 더 가깝다고 생각한다.

저장할 곳과 찾아갈 길이 정해지면 전체 설계가 보인다

지금까지 데이터베이스의 행과 열, 기본키와 외래키, 정규화와 인덱스를 차례로 살펴보았다. 행과 열은 값을 일정한 구조에 담고, 기본키는 한 대상을 구별하며, 외래키는 나뉜 기록의 관계를 잇는다. 정규화는 같은 사실의 반복을 줄이고, 인덱스는 많은 기록 속에서 필요한 값으로 가는 길을 만든다.

이 개념들은 따로 만들어진 기능이 아니다. 현실의 사람, 주문, 장비와 사건을 데이터베이스 안에서 어떤 모습으로 표현할 것인지 결정하는 과정의 일부다. 무엇을 하나의 대상으로 볼지, 어떤 값으로 구별할지, 어떤 관계를 남길지에 따라 테이블의 구조와 검색 방식이 함께 달라진다.

다음 글에서는 이 선택을 하나의 설계로 묶는 데이터 모델을 살펴보려 한다. 현실의 업무를 테이블과 관계로 옮길 때 무엇부터 정해야 하는지 이해하면, 데이터베이스가 단순히 표를 모아 둔 저장소가 아니라 정보체계의 뼈대라는 점도 조금 더 분명해질 것이다.