앞선 글에서는 스프레드시트보다 데이터베이스(DBMS)가 훨씬 강력한 이유(데이터 무결성, 보안, 백업, 커뮤니티 지원)를 다뤘다.
이제 “왜 실무에서 DBMS가 필수가 되었는가?”에 대해 어느 정도 감이 잡힐 것이다.
하지만 한 가지 더 중요한 궁금증이 있다.
단순히 “신뢰성”이나 “보안” 때문만이 아니라,
과연 데이터베이스(DBMS)가 수십만, 수백만, 수천만 건의 데이터를 다룰 때
스프레드시트(엑셀, 구글시트)와 성능 면에서도 정말 차이가 날까?
정답은 ‘그렇다’다.
특히 “빠른 데이터 검색”에서 DBMS는 더욱 강점을 갖는다.
이 모든 차이의 핵심에는 바로 ‘인덱스(색인)’라는 개념이 있다.
출처: https://ittrue.tistory.com/331
인덱스(색인)란,
데이터 검색 속도를 극적으로 높이기 위해
별도로 만들어 놓는 자료구조다.
스프레드시트/CSV는 ‘위에서부터 쭉’ 전체를 훑는 방식(Full Scan)만 지원한다.
반면, DBMS는 인덱스를 활용해
필요한 데이터 위치를 단 몇 번의 비교만에 찾는다.
인덱스를 만들기 위한 대표 자료구조는
B-Tree, B+Tree, Hash테이블가 있다.
출처: https://goodgid.github.io/Hash-Table/
해시 테이블은 데이터를 (Key, Value) 쌍으로 저장하는 자료구조다.
특정 Key를 입력하면, 해시 함수를 거쳐 그에 해당하는 위치(Index)를 바로 찾아내기 때문에
검색 속도가 O(1)로, 사실상 ‘즉시’ 데이터를 꺼낼 수 있다.
DB 인덱스에 해시 테이블이 도입될 때는
“컬럼 값(데이터)” → “데이터의 실제 위치”
이렇게 맵핑하는 방식으로 활용되는데,
특정 값을 가진 데이터가 데이터 파일의 어디에 저장되어 있는지
해시 테이블에 기록해 두고,
이후 해당 값으로 검색할 때 아주 빠르게 찾을 수 있다.
하지만 아쉽게도 데이터 베이스에서 해시 기반 인덱스는 잘 쓰이지 않는다.
바로 “동등(=) 비교”에만 특화되어 있기 때문이다.
해시 함수의 원리는
Key 값이 조금이라도 다르면 완전히 다른 해시값을 만들어내기 때문에
“값이 딱 일치하는 경우”에는 매우 빠르지만,
“범위 검색”이나 “LIKE ‘가나다%’”
처럼 부등호/와일드카드/범위 연산이 들어가면
해시 인덱스는 효율이 떨어지고,
결국 모든 데이터를 다 뒤져야 하기 때문이다.
이런 한계 때문에
DBMS에서는 실제로 범위 탐색, 정렬 등 다양한 쿼리가 자주 쓰이는 현실을 고려해
B+Tree 구조가 표준 인덱스로 선택 되었다.
그래서 DBMS는 B+Tree 구조를 주로 사용한다.
출처: 코딩애플
다만, 범위 검색을 할 때는 자식 노드의 범위를 넘어가면 다시 부모노드로 갔다가 다른 범위 노드로 가야 하기 때문에, 데이터를 찾는 과정이 비효율적인 경우가 있다
출처: 코딩애플
B+tree는 B-tree에서 최하단 노드(리프노드)에만 데이터를 보관한다.
그 외의 노드는 인덱스 노드라고 하며 데이터를 찾기 위한 가이드를 제공한다.
그리고, 리프노드 사이는 LinkedList로 연결한다.
이를 통해 범위 검색을 효율적으로 할 수 있다.
표로 만든 예시
트리 형태로 바꿔본 예시
처음에는 테이블에 데이터가 아무런 순서 없이 저장되어 있다.
하지만 클러스터 인덱스를 생성하는 순간, DBMS는 인덱스 기준이 되는 컬럼(PK 등)으로
모든 데이터를 자동으로 정렬한다. 이 과정에서 기존에 흩어져 있던 데이터들이
인덱스 기준에 따라 재배치되며, 여러 데이터 페이지로 나뉜다.
각 데이터 페이지는 정렬되고,
페이지마다 그 범위의 첫 번째 값을 대표로 가진다.
그리고 이 대표 값들과 페이지의 주소 정보가 별도의 루트 페이지(=인덱스 페이지)에
정리되어 저장된다.
데이터를 삽입할 때는
단순히 마지막에 추가되는 게 아니라,
인덱스 정렬 기준에 따라 자동으로 위치가 조정된다.
새로운 데이터가 들어오면,
해당 데이터가 들어가야 할 위치를 찾아서 삽입,
만약 한 페이지가 꽉 차면,
페이지 분할이 일어나고,
인덱스(루트/상위 노드) 정보도 같이 조정된다.
즉, 데이터 정렬을 계속 유지해야 하므로
INSERT/UPDATE의 비용이 다소 늘어날 수 있다.
실제로 쿼리문을 사용하면
대부분 두 인덱스를 혼합해서 쓰게 된다.
SELECT *
FROM 학생
WHERE 이름 = '홍길동';
동작 절차
1. 보조 인덱스(B+Tree, 이름 인덱스)에서 이름을 찾는다
2. 해당 이름에 연결된 학번(Primary Key, row pointer)을 찾는다
3. 클러스터형 인덱스(Primary Index, 학번 인덱스)에서 실제 레코드를 찾는다
4. 실제 데이터(행 전체)를 읽어 결과로 반환한다
참고: 보조 인덱스는 데이터의 주소값을 갖고 있다.
클러스터형 인덱스에 데이터가 늘어나면 보조 인덱스 구조도 계속 바뀐다.
인덱스를 혼합해서 쓸 때에는 보조 인덱스의 구조가 바뀌어야 할 수 있고,
데이터 검색 시 손해를 볼 수도 있지만
데이터를 수정/삭제할 때 효율적으로 관리할 수 있다.모두 제거해야 할 때 보조 인덱스부터 삭제해야 하는 이유:
보조 인덱스는 클러스터형 인덱스를 참조하는 구조이므로
클러스터형 인덱스를 먼저 지우면
보조 인덱스의 구조가 깨진다.
인덱스가 많아질수록
조회(SELECT) 성능은 반대로
인덱스 자체가 별도의 파일/디스크 공간을 차지
→ 저장 공간 관리 필요
불필요한 인덱스를 많이 만들면 오히려 성능이 저하
→ 정기적으로 인덱스 점검, 적정 개수 유지 필요
인덱스는 단순히 성능 향상을 넘어, 대규모 데이터베이스 운영에서 필수적인 요소임을 새삼 느꼈습니다.
이번 글을 준비하면서 저 역시 “인덱스가 실제로 어느 정도 성능 차이를 만들어낼까?”가 궁금해졌는데요.
실제로는 단순히 인덱스를 건다고 무조건 빠른 건 아니고,
테이블 구조·데이터 양·쿼리 패턴에 따라 인덱스 효과가 천차만별이라고 합니다.
그래서 직접 샘플 데이터를 mysql 공홈에서 받아서
인덱스의 유무, 인덱스 종류(B+Tree, 해시 등),
여러 쿼리로 실험해보고 싶습니다.
가능하면 진행하고 벨로그에 올리겠습니닷. (가능하면..)
참고 레퍼런스