
인덱스는 데이터베이스에서 데이터의 빠른 접근과 검색을 위해 사용되는 자료구조이다.
데이터베이스 튜닝
튜닝이란 SQL 서버가 기존보다 더욱 좋은 성능을 내도록 하는 전반적인 방법론을 말한다. (자동차의 성능을 높이기 위한 자동차의 튜닝과 개념이 비슷하다.)
튜닝은 크게 두 가지 관점으로 볼 수 있다. 하나는 응답시간(Response Time)을 빨리하는 것이다. 즉, A라는 사용자가 쿼리문을 실행하면 '얼마나 빨리 결과를 얻는가'의 문제가 관점이 된다. 이것은 사용자의 입장에서는 아주 효과적인 것처럼 보일 수 있지만, 잘 생각해 봐야 한다. A 사용자는 기존에 1분 걸리던 것을 10초 만에 얻게 된다면, 아주 효과적으로 보일 수도 있고 마치 튜닝이 잘 된 것처럼 보여질 수는 있다. 하지만 서버의 입장에서는 기존에는 1만큼의 작업만 하던 것을 100의 작업을 해야 하는 경우도 발생할 수 있다. 이런 경우에는 한 명의 사용자에게는 결과가 빨리 나오겠지만, 전체적인 시스템의 성능은 오히려 나빠질 수도 있다.
또 다른 하나는 서버의 부하량을 최소화하는 것이다.
즉, 한 명 한 명 사용자의 응답시간보다는 서버가 처리하는 총 작업량을 줄임으로써 시스템의 전바적인 성능을 향상시켜 서버가 더 많은 일을 할 수 있도록 하는 것이다.
물론, 사용자의 응답 속도가 빠르다는 것은 서버에서 처리를 조금만 하도록 해서 빨라지는 경우도 많지만, 그렇지 않은 경우도 종종 있으므로 주의해야 한다.
OLTP와 OLAP에 인덱스 생성
OLTP(On-Line Transcation Processing)는 INSERT/UPDATE/DELETE가 실시간으로 자주 발생되므로, 꼭 필요한 인덱스만 최소로 생성하는 것이 바람직하다. 하지만, OLAP(On-Line Analytical Processing)는 INSERT/UPDATE/DELETE가 별로 사용될 일이 없으므로 되도록 인덱스를 많이 만들어도 별 문제가 되지 않는다. 만약, 하나의 DB가 OLAP/OLTP 겸용으로 사용된다면 두 개를 분리하는 방법을 고려하는 것이 전반적인 시스템의 성능에 도움이 될 것이다.
테이블 생성 시에 제약 조건 Primary Key 또는 Unique를 사용하면 자동으로 인덱스가 자동 생성된다.
자동으로 인덱스가 생성되는지 확인해보자.
Primary Key


Unique Key


Primary key가 없고, Unique Key에 NOT NULL을 포함하면 클러스터형 인덱스가 된다.


Unique Key에 NOT NULL을 포함해도 Primary Key가 있다면 클러스터형 인덱스는 테이블당 하나만 지정되므로 Primary Key를 우선적으로 클러스터형 인덱스가 생성된다.


PRIMARY KEY로 지정한 열로 데이터가 오름차순 정렬된다.



Primary key가 없고, Unique Key에 NOT NULL을 포함하면 클러스터형 인덱스가 된다는데 진짜인가? 오름차순 정렬이 되나?



제약 조건을 설정할 때 주의할 점
제약 조건의 설정은 대개 테이블의 생성 구문에서 하거나 Alter문으로 생성한다. 그러므로 아직 데이터가 입력되기 전에 Primary Key 및 Unique 키의 열에는 인덱스가 생성되어 있기 때문에, 인덱스 자체를 구성하는 시간이 걸리지 않는다. 하지만, 많은 데이터가 입력된 후에 Alter문으로 Unique나 Primary를 지정하면 인덱스를 구성하는 데 많은 시간이 걸릴 수도 있다. 즉, 업무시간에 함부로 기존에 운영되는 대량의 테이블의 인덱스를 생성하면 시스템이 엄청나게 느려져 심각한 상황이 발생될 수도 있으니 주의해야 한다. (최신의 MySQL 버전에서는 이전 버전에 비해 인덱스 생성 속도가 향상되기는 했지만, 그래도 데이터의 양에 따라서 몇 시간이나 그 이상의 시간이 걸리 수도 있다.)
클러스터형 인덱스
루트 페이지를 읽어서 다음 검색할 곳을 찾는다.
그렇게 리프 페이지까지 찾아가고 리프페이지를 읽어서 데이터를 출력 한다.
보조 인덱스
루트 페이지를 읽어서 다음 검색할 곳을 찾는다.
그렇게 리프 페이지까지 찾아가고 리프페이지를 읽어서 데이터 페이지의 RID를 알아낸다.
RID를 이용해 데이터를 출력 한다.
클러스터형 인덱스와 보조 인덱스가 혼합되어 있을 경우
보조 인덱스의 루트 페이지를 읽어서 다음 검색할 곳을 찾는다.
그렇게 리프 페이지까지 찾아가고 리프페이지를 읽는다.
클러스터형 인덱스가 없었다면 RID를 알아냈겠지만, 클러스터형 인덱스가 혼합되어 있기 때문에 클러스터형 인덱스의 키 값을 알아낸다.
다시 클러스터형 인덱스의 루트 페이지를 읽어서 다음 검색할 곳을 찾는다.
리프 페이지(==데이터 페이지)까지 찾아가고 리프페이지를 읽어서 데이터를 출력 한다.
클러스터형 인덱스와 보조 인덱스를 분리해서 서로 관련 없이 구성한다면 검색에서는 더 우수한 성능을 보일 것이다. 하지만, 치명적인 단점 때문에 그렇게 구성되지 않는다. 클러스터형 인덱스의 페이지 분할이 발생될 때 리프 페이지가 재구성되어서 데이터 페이지의 번호 및 오프셋이 대폭 변경된다. 이로 인해 보조 인덱스 역시 많은 부분이 다시 구성되어야만 한다. 이는 엄청난 시스템의 부하를 발생시킬 소지가 있다. 이러한 이유때문에 서로 관련없이 구성해서 검색의 빠른 성능을 보장 받는것보다 보조 인덱스에서 검색하고나서 클러스터 인덱스를 검색하는 약간의 손해를 감수하고서라도 시스템의 부하를 줄이려고 구성하는 것이다.
혼합 인덱스에서 인덱스 지정 시 고려사항
클러스터형 인덱스를 지정할 열의 자릿수가 크다면 보조 인덱스에 저장되어야 할 양도 더불어서 많아진다. 그러면, 차지하는 공간이 자연히 커질 수밖에 없다. 결국 보조 인덱스와 혼합되어 사용되는 경우에는 되도록이면 클러스터형 인덱스로 설정할 열은 적은 자릿수의 열을 선택하는 것이 바람직하다.
약 30만 개의 데이터로 성능을 테스트 해보자.

인덱스가 없을 때

Full Table Scan
2093 - 1060 = 1033
클러스터형 인덱스만 있을 때

클러스터형 인덱스 사용
878 - 875 = 3
보조 인덱스만 있을 때

보조 인덱스 사용
871 - 866 = 5
인덱스가 없을 때



Full Table Scan
1930 - 872 = 1058
클러스터형 인덱스만 있을 때


Index Range Scan
872 - 867 = 5
보조 인덱스만 있을 때



Index Range Scan
query_cost = 449.81
1501 - 868 = 633
클러스터형 인덱스에서 전체 데이터를 읽기



Index Range Scan
1752 - 864 = 888
Data Read: 19M
인덱스 힌트는 강제로 사용하는 'USE INDEX(인덱스이름)'와 강제로 사용하지 못하게 하는 'IGNORE INDEX(인덱스이름)' 정도만 알고 있으면 된다.
IGNORE INDEX(PRIMARY) 힌트를 사용해서 전체 데이터를 읽기


Full Table Scan
1754 - 866 = 888
Data Read = 12M
비록 이 경우에는 데이터를 읽은 양(Data Read)이 인덱스를 사용한 것보다 적기 때문에, 전체 테이블 검색이 효율적으로 보일 수 있다. 하지만, 전체적인 효율은 다양한 시스템의 상황이나 데이터 분포 등에 영향을 받을 수 있기 때문에, 읽은 양이 적다고 반드시 효율적이라고 보기는 어렵다. 이 예에서도 인덱스를 사용한 쿼리가 'Cost Info' 부분의 읽기(Read) 부분만 비교하면 적게 나오기 때문에 효율적이라고 볼 수도 있다.
IGNORE INDEX(idx_emp_no) 힌트를 사용해서 전체 데이터를 읽기



Full Table Scan
query_cost = 30188.05
1929 - 871 = 1058
범위를 늘려서 쿼리를 성능 테스트


Full Table Scan
1937 - 867 = 1070
인덱스가 있지만 인덱스를 사용하지 않고 전체 테이블을 검색했다.
MySQL은 인덱스와 Full Table Scan 중 더 빠른 방법을 판단하여 사용한다.
WHERE 조건이 없는 전체 데이터를 조회


Full Table Scan
1761 - 873 = 888