SSAFY 프로젝트 발표회를 보면서, 기억에 남은 것 중 하나가 인덱스였다.
자율프로젝트 쯤 되니, 인덱스를 사용하여 쿼리 조회 시간을 단축시켰다는 내용이 심심치 않게 소개되었던 탓이다.
어떻게?
말 그대로 index를 부여해서!
primary key, 혹은 unique not null 제약조건을 가지고 있는 속성을, int(혹은 varchar)와 한데 묶어 인덱스를 생성시켜 놓는 것이다.
그리 함으로서, 이제 우리는 Select 요청이 들어왔을 때, B-Tree로 구성된 logN * Depth 시간복잡도만 소요하여 데이터를 읽어들일 수 있게 되었다.
primary key 속성을 사용 시: 클러스터 인덱스
unique not null 속성을 사용 시 : 세컨더리 인덱스
Select(조회)가 빨라진다! - 자료가 많은 경우
검색 성능 향상: 인덱스는 데이터의 검색 속도를 빠르게 한다. 인덱스를 통해 검색하려는 데이터의 위치를 더 빠르게 찾을 수 있기 때문.
정렬된 데이터 접근: 인덱스는 주로 B-tree(이진 트리랑 다르다!) 구조를 사용하기 때문에, 데이터를 정렬된 상태로 저장하고 접근한다. 이를 통해 ORDER BY나 GROUP BY와 같은 쿼리를 빠르게 수행할 수 있다.
장점만 있는 것은 아니다.
저장 공간: 인덱스 자체가 데이터를 복사하여 저장하므로 추가적인 저장 공간을 필요로 한다.
데이터 수정의 오버헤드: 데이터의 추가, 삭제, 수정이 발생하면 인덱스도 함께 갱신되어야 한다. 이러한 특성 때문에, 인덱스의 생성 여부를 정할 때에는 수정이 잦은 테이블보다, 읽기가 주로 이루어질 테이블에 생성하는 것이 좋다.
대부분의 데이터베이스에서, 인덱스를 구현하기 위한 자료구조로 B-tree 자료구조를 선택하여 사용한다.
B-tree는 이진 탐색 트리를 확장한 것으로, 노드당 여러 키를 가질 수 있고 여러 자식을 가질 수 있는 이진 트리이다.
루트(root) - . . . 브랜치(branch) . . . - 리프(leaf)
B-tree의 생성: 인덱스 생성 시, 트리의 각 노드에는 키와 키에 해당하는 데이터의 위치가 저장된다.
검색: 키 값을 사용해 B-tree를 탐색한다. 이진 탐색 트리와 마찬가지로, 키가 현재 노드의 키보다 작으면 왼쪽 자식을, 크면 오른쪽 자식을 방문한다.
삽입: 새 데이터를 삽입할 때는 해당 키를 B-tree에 추가한다. 이 과정에서 트리의 밸런스를 유지하기 위해 Depth가 깊어질 수 있다.
삭제: 데이터를 삭제할 때는 해당 키를 B-tree에서 제거합니다. 이 과정에서 트리의 밸런스를 유지하기 위해 Depth가 얕아질 수 있다.
갱신: 데이터를 갱신할 때는 해당 키를 B-tree에서 제거하고 새 키를 삽입하여 처리한다.
인덱스에 사용된 B-tree 자료구조는 검색, 삽입, 삭제 연산 모두 O(log n)의 시간 복잡도를 가져, 대량의 데이터에서도 빠른 성능을 보장한다.
하지만! 삽입, 삭제의 경우 Depth 재정렬이 일어날 수 있으므로 오버헤드에 주의해야 한다.
또한 공간적(별도의 공간 필요), 시간적(별도의 인덱스 서칭 시간) 자원의 소모되므로, 인덱스가 필요한 테이블인지 고민한 뒤 선택함이 옳을것이다.
DB 설계 시 데이터량에 따라 인덱스 사용 여부를 고려해보아야겠네요