인덱스
데이터를 찾는 두 가지 방법이 있다고 하자 어떤 초등학교를 방문해 홍길동을 찾는 방법은 두 가지다 첫째는 1학년 1반부터 6학년 맨 마지막 반 모두를 돌며 홍길동을 찾는 것이고 두 번째는 교무실에서 학생명부를 받아서 교실로 찾아가는 방식이다 이때 홍길동 학생이 많으면 많을수록 전자가 빠르고 적으면 적을 수록 후자가 빠르다 그래서 학생명부는 이름을 기준으로 학년 - 반 - 번호 이렇게 관리하는 게 학생을 찾는 것이 수월하다고 판단해서 만들어졌다
이름 | 학년 - 반 - 번호 |
---|---|
강수지 | 4학년 3반 37번 |
김철수 | 3학학년 2반 13번 |
… | … |
이영희 | 6학년 4번 19번 |
… | … |
홍길동 | 1학년 5반 15번 |
… | … |
이때 학년 - 반 - 번호에 해당되는 데이터가 바로 ROWID이다 데이터베이스 테이블에서 데이터를 찾는 방법도 수십 년 동안 2가지에서 벗어나지 못하고 있다
- 테이블 전체를 스캔한다
- 인덱스를 이용한다
이를 위의 상황과 빗대어서 연결하면 테이블 전체를 스캔하는 것은 1학년 1반부터 6학년 끝반까지 일일이 돌며 홍길동을 찾는 것이고 인덱스를 이용하는 게 학생명부를 활용해서 찾는 것을 말한다
인덱스 튜닝의 두 가지 핵심요소
인덱스는 큰 테이블에서 소량 데이터를 검색할 때 사용한다 핵심적인 요소는 크게 두 가지로 나뉜다 첫 번째는 인덱스 스캔 과정에서 발생하는 비효율을 줄이는 것이다 즉 인덱스 스캔 효율화인 것이다 만약에 학생 명부를 다음과 같이 정렬해두었다고 했을 때 어디가 더 비효율적인지 살펴보자 이때 시력이 1.0 ~ 1.5인 홍길동을 찾는다고 해보자
- 학생명부를 이름과 시력순서로 정렬
이름 | 시력 | 학년 - 반 - 번호 |
---|---|---|
강수지 | 1.5 | 4학년 3반 37번 |
김철수 | 0.5 | 3학년 2반 13번 |
… | … | … |
이영희 | 1.5 | 6학년 4번 19번 |
… | … | … |
홍길동 | 1.0 | 1학년 5반 15번 |
홍길동 | 1.5 | 2학년 6반 24번 |
홍길동 | 2.0 | 5학년 5반 15번 |
… | … | … |
이름과 시력 순서로 되어 있기 때문에 바로 2명인 것을 체크할 수 있다 다른 데이터를 볼 이유가 없다
- 시력과 이름순서로 정렬
시력 | 이름 | 학년 - 반 - 번호 |
---|---|---|
0.5 | 김철수 | 3학년 2반 13번 |
… | … | … |
1.0 | 홍길동 | 1학년 5반 15번 |
1.5 | 강수지 | 4학년 3반 37번 |
1.5 | 이영희 | 6학년 4번 19번 |
1.5 | 홍길동 | 2학년 6반 24번 |
… | … | … |
2.0 | 홍길동 | 5학년 5반 15번 |
… | … | … |
이렇게 되면 처음 홍길동을 만나고 강수 이영희를 띄어넘고 홍길동을 찾아야 한다 그리고 1.5 와 2.0 사이에 시력을 가진 홍길동이 있는지 또 찾아봐하는 비효율이 발생한다 즉 데이터의 스캔 범위가 늘어나게 된다 인덱스 튜닝의 두 번째 핵심 요소는 테이블 액세스 횟수를 줄이는 것이다 인덱스 스캔 후 테이블 레코드를 액세스할 때 랜덤 I/O 방식을 사용하므로 이를 랜덤 액세스 최소화 튜닝이라고 한다
튜닝은 랜덤 I/O 와의 전쟁
데이터 베이스가 데이터를 가져오기 위해 디스크 접근 시 보다 적은 블록에 접근해서 만족하는 결과를 가져오는 것이 성능에 지대하게 영향을 미친다 CPU의 프로세서 속도는 매우 빠르지만 디스크의 I/O 성능은 CPU 입장에서는 매우 느린 편이다 그래서 대량의 데이터를 가져올 때는 프로세서가 수시로 잠들고 깨어나길 반복하게 되는데 이때 잠이 드는 횟수가 많으면 많을수록 성능은 떨어지게 된다 그렇기에 SQL의 성능은 디스크의 I/O 그중에서 랜덤 I/O에 의해서 결정이 된다
인덱스 구조
만약 어떤 책을 보고 어떤 단어를 찾을 때 어떻게 하는가 책 제일 뒤에 색인이 있으면 그것을 보고 해당 단어의 페이지를 보고 바로 찾아갈 수 있지만 그런 페이지가 없다면 독자는 처음부터 끝까지 다시 읽으면서 해당 단어가 어디서 나왔는지 찾아야 한다 데이터베이스도 마찬가지다 인덱스 없이 데이터를 검색하려면 테이블을 처음부터 끝까지 모두 읽어야 한다 반면 인덱스를 사용하면 범위 스캔이 가능함으로 일부만 읽고 끊을 수 있다 이때 범위 스캔이 가능한 이유는 인덱스가 정렬돼 있기 때문이다
B Tree
이런 모양이며 이는 나무를 뒤집었다고 생각하면 된다 뿌리가 제일 위에 있고 잎이 밑에 있는 그런 구조이다 우리는 여기서 뿌리를 root라고 할 것이고 가지를 branch라고 할 것이고 잎을 leaf라고 할 것이다
루트와 브랜치 블록에는 각 레코드의 하위 블록에 대한 주솟값을 갖는다
이 그림은 root 와 branch를 나타내었다 이렇게 루트는 브랜치에 대한 주솟값을 가지고
이 그림은 branch 가 leaf를 주소로 연결하고 있는 것을 보여주고 있으며 각 leaf 또한 double linked list로 연결되어 있습니다 이때 double linked list는 각 노드가 앞뒤로 연결되어 있다는 연결 리스트로 단일 연결 리스트와 달리 이전 포인터와 이후 포인터를 둘 다 가지게 되는데 이는 리프 노드끼리 연결되어 있어서 인덱스 범위 스캔을 빠르게 해주는 역할입니다 그럼 대략 B Tree의 기초적인 구조만 설명을 했고 안에 내용에 대해서는 다음 장에서 계속하도록 하겠습니다