오늘은 데이터의 관리가 아닌, 사용성에 대해 논의해보자! 사용자가 데이터를 요구할 때, 사용자의 편리성을 위해 어떻게 하면 빠르게 정보를 제공할 수 있을까? 이 문제에 답변하기 위해 인덱스에 관해 논해봅시다!
데이터가 디스크에 저장되어있고, 사용자가 특정 자료(COM12)를 요구하면, 메모리에 해당 데이터를 불러와서 대답하는 처리과정을 겪는다. (이전시간에 이걸 배웠다!) 이 지시사항은 간단하나, 만약 사용자가 수천, 수만개의 자료를 요구한다면 디스크와 메모리사이에 수없이 많은 데이터 I/O가 발생하게 되는데, 이는 오랜 시간을 요구하고 DBMS의 성능을 악화시킬 수 있다.
그렇다면, I/O를 줄일 수 있는 방법은 뭘까? 이걸 논의하기 위해 인덱스가 개발되었다!
탐색키: 파일에서 레코드를 찾는데 사용되는 컬럼이나 컬럼의 집합
기본키 외래키와 같은 키값이 아니라, 레코드를 검색할 때 사용할 수 있는 키를 "탐색키"라고 한다!
인덱스가 컬럼이구나.. list의 자료들을 확인할 때 for문의 i를 인덱스로 접근한다고 배웠는데, 그 인덱스가 이 인덱스일까? 그러고보니 정확한 작동 원리를 궁금해하지 않았던것 같다. 넘모재밋다.
인덱스 사용 검색과정은, 사용하지 않는 검색과정과 어떤 차이점이 있을까?
탐색키가 과목 코드로 되어있고, 과목코드와 관련된 데이터가 화살표로 지칭되어 있음. 만약 사용자가 "COM12"를 물어보면, DBMS는 요구 자료와 관련된 레코드 블럭을 올리는것 아니라, 먼제 메모리에 인덱스 블럭을 올린다. 이를 통해 COM12의 위치가 파악하고, 그 위치가 있는 블럭을 불러와서 제공한다.
언뜻 보기에는 한 단계가(인덱스를 메모리에 올리는) 증가하여 더 복잡해 보일 수 있으나, 실제로는 속도면에서 매우 빠르다. 인덱스는 실제 레코드 블럭보다 훨씬 사이즈가 작기 때문에 굉장히 많은 인덱스의 양을 올릴 수 있고, 그래서 그 위치를 빠르게 알 수 있다. 그에 반해 바로 레코드 블럭을 메모리에 올려 레코드를 찾는 것은, 레코드의 양이 많아지는 경우 한번에 많은 양의 레코드를 메모리에 적재할 수 없기 때문에 해당 레코드 위치를 찾는데 오랜 시간이 걸리게 된다.
인덱스의 종류
인덱스의 평가 기준
순서 인덱스: 모든 인덱스의 탐색키들이 순차적으로 정렬되어 있다! = 빠른 접근
예시: 사전 (day라는 단어를 찾고 싶으면 D부터 찾으면 됨. 아주 빠르게 검색가능)
파일 구조화 3가지: (힙, 순차, 해쉬) 파일 구조. 각각의 레코드를 실제 디스크에 어느 레코드블럭에 저장해야할지 고민하는 개념.
각각의 레코드가 과목코드 순으로 정렬되어 있음. 순차파일들은 연속된 레코드를 정렬하기 위해 다음 레코드를 지목하는 포인트를 가져 다음 레코드에 빠르게 접근하도록 도움. 순서 인덱스는 이 순차 파일처럼 저장된 레코드를 대상으로 만든 인덱스!
인덱스는 어떤 구조를 가지고 있을까?
인덱스 엔트리: 각각 레코드에 대한 빠른 접근을 할 수 있는 작은 형태의 테그(레이블).
김영희를 가르키는 인덱스 엔트리. b2 블럭의 30byte뒤에 있음을 의미함.
빡빡하게 모여있다!
데이터베이스시스템의 인덱스(COM31)를 찾기 위해 I/O의 기본 단위만큼 차근차근 메모리에 적재되어 COM31을 찾을때까지 넣었다뺐다 반복. 만약 COM31 인덱스를 찾으면 해당 블럭을 메모리에 올려서 데이터 찾아옴.
만약 COM31을 메모리에 적재하기 위해서는 어떻게 해야할까?
곧바로 찾아갈 수 있는 인덱스 엔트리가 없으므로, COM31보다 작은 엔트리 중 가장 큰 것(COM11)을 따라가서, COM31이 나올때까지 순차 레코드를 따라간다.
밀집 인덱스
- 장점: 해당 레코드로 곧바로 이동가능(경로를 제공받음)
- 단점: 인덱스의 크기가 매우 커짐.
희소 인덱스
- 장점: 인덱스의 크기가 작아 인덱스를 찾는 비용 작음.
- 단점: 특정 인덱스를 찾기 위해 순차파일 내부에서 일부 검색을 다시
뭐가 더 좋고 나쁜가는 상황에 따라 달라진다. 차이점에 초점!
밀집 인덱스의 한 예시. 위같은 예시에서 인덱스 자체만으로 4GB의 공간을 사용해야만 한다. 레코드를 찾기 위해서는 먼저 메모리에 4GB(인덱스의 양)를 적재해야 함. 메모리에 공간이 없이면 특정 레코드를 찾기 위해 수백, 수천번의 I/O를 거듭해야 한다.
아.. 인덱스 쓰는거만해도 넘 힘든데, 인덱스 잘 쓰기 위한 방법이 없을까?를 논의하기 위해 다단계 인덱스가 나옴!
그러면, 지금 인덱스도 너무 크잖아? 인덱스를 찾기 위한 인덱스를 또 만드는건 어떨까? = 다단계 인덱스
밀집+희소
내부 인덱스는 밀집 인덱스로 만들고, 밀집 인덱스의 각 블럭에 대한 인덱스를 만들면, 이건 희소 인덱스와 유사해짐.
내부 인덱스(=밀집 인덱스)에 대한 또 다른 인덱스 외부 인덱스(=희소 인덱스)를 만든다! 외부 인덱스는 범위정도를 알려줄 수 있다.
특정 다단계 인덱스
이 카드들을 모두 뒤집어놓고 7을 찾으라고 하면 어떻게 할까요?
1. 첫번째부터 뒤집어본다.
2. 뒤에서부터 뒤집어본다.
3. 무작위로 잡히는걸 뒤집어본다.
=> 평균적으로 카드를 절반개정도 뒤집으면 알수 있다. 만약 카드가 1억개면, 5천만번 정도 뒤집어봐야 알 수 있다. 이 카드를 정렬해놓으면 훨씬 빠르게 원하는 카드를 찾을 수 있다!
Q. 7을 찾고 싶으면 어디를 뒤집으면 될까?
A. 중간쯤을 뒤집어보면 된다.
Q. 13을 찾으려는데, 뒤집은 카드가 10이다. 어디를 뒤집어보면 될까?
A. 오른쪽을 뒤집어 보면 된다.
이같은 방식을 이진 검색 트리라 한다.
다단계 인덱스도 이런 방식으로 구성되어 있다. 다단계와 이진탐색트리를 절묘하게 결합시켜 하나의 노드가 하나의 블럭사이즈가 되도록 담고, 두 갈래가 아닌 수십개, 사백개로 나뉘도록 하여 짧은 계층의 인덱스 구조를 제공하는 것이, B+ 트리이다!!
하나의 노드에 하나의 값이 아니라, 하나의 노드는 4KB정도가 되고 여러개의 탐색키와 그 하위노드를 가르키는 포인터를 담을 수 있도록 크기를 확장 해놓은 구조. (=B+트리) 실제 DB에는 이 탐색키가 100개정도 들어간다.
제일 밑의 노드가 서로 가리키고 있는 일종의 연결 리스트 형식으로 되어있다.
루트 노드로부터 모든 단말 노드에 이르는 경로의 길이가 같은 높이 균형 트리
B+ 트리의 노드 구조
각각의 노드는, 탐색키를 위한 칸이 존재하고, 탐색키보다 작은 값이 왼쪽에, 큰 값이 오른쪽에 위치하도록 한다. n-1개의 탐색키, 탐색키에 맞춰 하위노드를 가르키는 포인터가 총 n개 들어간다.
실제 레코드의 위치에 대해 알려주는 것이 아니라, 그 데이터가 위치하는 범위를 알려준다. 경로 제공이 목적!
루트노드 + 중간노드 인덱스 세트.
포인터는 레코드를 가르키는게 아니라, 그 하위의 토드를 가르키기 위함. COM34는 레코드의 위치 방향성 제시를 위함.
순차세트: 단말노드로 구성
모든 단말노드들은 형제노드가 무엇인지 포인터로 가르키고 있기 때문에 순차세트라고 부름
이 키값들은 그포인터(화살표)가 실제 디스크 내의 레코드 위치를 가리키고 있음.
과목테이블이 디스크에 저장되어 있으면, 이렇게 인덱스를 만들어 볼 수 있다. COM12, COM31은 실제 포인터에 대한 태그. 포인터는 디스크의 실제 데이터를 지목하고 있다.
요청: 'COM44'의 과목명은?
1.레코드 삽입, 삭제 시 B+ 트리 수정
삽입: 검삭과 같은 방법을 사용하여 삽입되는 레코드의 탐색키값이 속할 단말 노드를 탐색
삭제: 삭제될 레코드의 탐색키를 통해 삭제될 탐색키와 포인터를 포함한 단말 노드를 탐색
공간이 부족하므로 두개의 노드로 나누고, 부모의 노드도 하나씩 조정함
삽입 대상 노드에 추가적인 저장할 공간 부족: 노드분할
새롭게 추가된 단말 노드를 가리키는 포인터가 필요하므로, 부모노드도 수정해준다. 단말 노드를 밀어줬으므로, 가장 앞에있는 값인 COM24를 탐색기값에 넣고, 오른쪽의 포인터를 끝에 넣어준다.
< 삽입 최종 결과물 >
COM44 찾아서 삭제. (포인터도 같이 삭제)
COM12가 있는 단말 노드를 검색하고 탐색키를 삭제
=> COM12이 저장된 노드의 오른쪽 형제 노드와 키를 재분배!
< 삭제 Before vs After >
before | after |
---|---|
![]() | ![]() |