RealMySQL 책을 읽고 노션에 정리한 글을 옮긴 것입니다.
기계식 장치 - 하드 디스크 드라이브, 전기적 장치 - 솔리드 스테이트 드라이브
성능 → ssd > hdd
랜덤 I/O 표현은 하드 디스크의 원판(플래터)를 돌려서 읽어야 할 데이터가 저장된 위치로 디스크 헤더를 이동시킨 다음 데이터를 읽는것을 의미한다. 순차 I/O 도 동일하다.
랜덤 I/O 는 3개의 데이터를 찾기 위해서 디스크의 헤더를 3번 이동시키고, 순차 I/O 는 3개의 데이터를 찾기위해 디스크의 헤더를 1번만 이동시킨다. 즉, 순차 I/O는 랜덤 I/O 보다 3배 더 빠른데 그 속도를 결정하는게 디스크 헤더의 움직임이다.
이러한 랜덤 I/O 의 속도를 보완하기 위해 InnoDB 의 버퍼 풀이 존재한다.
SSD 는 원판이 없다고 해서 랜덤 I/O 랑 순차 I/O 차이가 안나는게 아니다. 순차 I/O 가 더 빠른다.
쿼리의 성능을 향상하기 위해서는 순차 I/O 를 발생시키도록 하는게 아닌 랜덤 I/O 의 횟수를 줄이도록 노력하는 것이다.
최상단 노드를 루트 노드, 가장 하위에 있는 노드를 리프노드, 루트노드와 리프 노드 사에이 있는 노드들을 브랜치 노드라고 한다.
루트 노드 페이지에 인덱스 키와 자식노드 주소가 Key, Value 형식으로 되어있으며
리프노드에는 Seconary Index (key) 와 프라이머리 key (value) 가 있는데 프라이머리 키를 통해서 다시 프라이머리로 구성된 b-tree(클러스터링 된 인덱스) 로 접근을 해서 리프노드에 있는 데이터를 가져오게 한다.
MyISAM 은 리프노드 에서 인덱스 키와 레코드 주소 쌍으로 데이터 파일에 다이렉트로 접근해서 데이터를 찾을 수 있다.
조건 검색 값이 프라이머리 키이면 프라이머리 키로 구성된 b-tree 로 접근을 해서 리프노드에 있는 데이터를 가져오면 되지만 secondary key 를 통해서 접근하게 된다면 그 secondary key의 리프노드에 있는 프라이머리 키를 가져와서 다시 프라이머리 키로 구성된 b-tree로 접근을 해야한다는 것이다.
디스크에 클러스터링된 인덱스 (Primary Key B-Tree), 세컨더리 인덱스 (Secondary Key B-Tree) 두가지 경우로 저장된다.
데이터를 리프노드에 추가 할때 리프 노드의 용량이 꽉 차게 되면 split되서 처리된다. 리프 노드만 split 되는 것이 아닌 상위 브랜치 노드 까지 처리 범위가 넓어 지기 때문에 쓰기 작업은 상대적으로 비용이 많이 든다.
PK , UK 중복 체크와 데이터 정렬을 위해서 B-Tree 에 즉시 추가한다.
세컨더리 인덱스(Secondary Index)는 체인지 버퍼에 의해서 지연 처리가 가능하다.
- 세컨더리 인덱스의 리프 노드는 데이터 자체가 아닌 인덱스 키 값과 프라이머리 키 값만 저장하므로, 실제 데이터 삽입과 독립적으로 처리 가능하다.
2. PK와 세컨더리 인덱스가 있는 테이블에서의 INSERT
(1) 테이블 구조
CREATE TABLE employees (
id INT PRIMARY KEY, -- PK (즉시 처리)
name VARCHAR(50),
department VARCHAR(50),
INDEX (department) -- Secondary Index (지연 처리 가능)
);
(2) INSERT 동작
INSERT INTO employees (id, name, department) VALUES (1, 'Alice', 'HR');
과정
프라이머리 키 B-Tree (즉시 처리):
id = 1
이 프라이머리 키로, 즉시 클러스터링된 인덱스에 추가됩니다.id
의 중복 여부를 검사한 뒤, id = 1, name = 'Alice', department = 'HR'
데이터를 클러스터링된 인덱스(B-Tree)에 저장합니다.세컨더리 인덱스 B-Tree (지연 처리 가능):
- 세컨더리 인덱스 department
는 삽입 시 바로 B-Tree에 추가되지 않을 수 있습니다.
- InnoDB는 department = 'HR', id = 1
의 값을 메모리에 유지한 뒤, 필요할 때 B-Tree에 배치(batch)로 추가합니다.
- 배치 처리는 트랜잭션이 커밋될 때 또는 세컨더리 인덱스 플러시 작업 시 이루어질 수 있습니
해당 키 값이 저장된 B-Tree의 리프 노드를 찾아서 삭제 마크만 하면 된다. 마킹된 리프 노드는 방치하거나 재활용 된다. 삭제 작업도 디스크 쓰기 작업이기 때문에 I/O 가 들어가는 작업이다.
인덱스 상의 키 값을 변경하는 것이 아닌 인덱스 값이 저장될 리프 노드를 다시 재설정해야하는 작업이다.
B-Tree 키 값의 변경은 우선 인덱스 키 값을 삭제 후 새로운 키 값을 추가 하는 작업으로 DELETE → INSERT 작업으로 UPDATE 가 진행된다.
100% 일치 또는 값의 앞부분(Left-most part)만 일치하는 경우에 사용할 수 있다. 부등호(”>, <”)비교 조건에서도 인덱스를 활용 할 수 잇지만, 인덱스를 구성하는 키 값의 뒷 부분만 검색하는 용도로는 인덱스를 사용할 수 없다.
인덱스의 키 값에 변형이 가해진 후 비교되는 경우에는 절대 B-Tree의 빠른 검색 기능을 사용할 수 없다.
인덱스를 제대로 설계 안하면 레코드를 읽을때 모든 레코드를 잠금하기 때문에 잘 설계 해야한다.
자식 노드의 갯수는 데이터 페이지의 크기에 따라 달라진다. (가변적)
인덱스 페이지 16KB 에 Key 값 = (8 + 8) 바이트 + 자식 노드 주소 = 12 바이트
16 * 1024 / (16 + 12) = 585 개의 자식 노드를 가질 수 있다.
index 키 값의 크기가 기존보다 커진다고 하면 16 * 1024 / (32 + 12) = 372 개가 저장되는데 만약 테이블의 크기가 500 개의 인덱스가 들어가는 크기라면 첫번째 경우는 디스크 한번 읽어오면 되지만 후자의 경우는 디스크를 2번 읽어오게 된다.
인덱스의 키 값의 크기가 작으면 좋다
유니크한 값의 개수 = 기수성
케이스 A : country 칼럼의 유니크한 값의 개수가 10개
케이스 B : country 칼럼의 유니크한 값의 개수가 1000개
mysql> SELECT * FROM tb_test WHERE country = 'KOREA' AND city ='SEOUL'
A 케이스의 경우에는 평균 1,000건
B 케이스의 경우에는 평균 10건이 조회될 수 있다.
실제로 모든 조건을 만족하는 레코드가 1건만 있다면 케이스 A 인 경우에는 999건의 레코드를 불필요하게 읽은 것이고 케이스 B 인경우는 9건의 레코드를 불필요하게 읽은 것이다.
유니크한 값의 개수는 인덱스나 쿼리의 효율성에 큰 영향을 미친다.
옵티마이저에서는 인덱스를 통해서 레코드를 읽는 것이 테이블에서 직접 레코드를 읽는것 보다 4 ~ 5배 정도 비용이 더든다고 판단한다.
인덱스를 통해 읽어야 할 레코드의 건수가 전체 테이블레코드의 20~ 25%를 넘어서면 인덱스를 이용하지 않고 테이블을 모두 직접 읽어서 필요한 레코드만 가려내는(필터링)방식으로 처리하는게 효율적이라 판단한다.
인덱스 접근 방식중에 가장 대표적인 접근방식이다.
범위를 스캔하는것 WHERE =
보다는 >, < BETWEEN
같이 범위를 탐색할때 사용된다.
인덱스 레인지 스캔으로 3개의 데이터를 데이터 레코드에서 가져와야 한다면 3번의 I/O 처리가 발생한다. 인덱스를 통해서 읽어야 할 데이터의 양이 테이블의 20 ~ 25 % 가 넘으면 테이블 레코드 자체를 읽는것이 더 효율적이다.
인덱스 레인지 스캔이 일어나는 순서
1. 인덱스에서 조건을 만족하는 값이 저장된 위치를 찾는다. 이 과정을 인덱스 탐색(Index seek)이라고 한다.
2. 1번에서 탐색된 위치부터 필요한 만큼 인덱스를 차례대로 쭉 읽는다. 이 과정을 인덱스 스캔(Index scan)이라고 한다. (1번과 2번 합쳐서 인덱스 스캔으로 통칭하기도 한다.)
3. 2번에서 읽어 들인 인덱스 키와 레코드 주소를 이용해 레코드가 저장된 페이지를 가졌고, 최종 레코드를 읽어 온다.
인덱스 레인지 스캔과 달리 인덱스의 처음부터 끝까지 모두 읽는 방식 (리프노드의 페이지)
인덱스 리프 노드의 제일 앞 또는 제일 뒤로 이동한 후, 인덱스의 리프노드를 연결하는 링크드 리스트를 따라서 처음부터 끝까지 스캔하는 방식을 인덱스 풀 스캔이라고 한다.
이 방식은 인덱스 레인지 스캔보다 빠르지 않지만 테이블 풀 스캔보다는 효율적이다. 인덱스에 포함된 칼럼만으로 쿼리를 처리할 수 있는 경우
테이블 employees 가 다음과 같이 구성
```sql
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(50),
salary INT,
department_id INT,
INDEX idx_salary_department (salary, department_id)
);
```
```sql
SELECT salary, department_id FROM employees ORDER BY salary;
```
**왜 인덱스 풀 스캔이 효율적인가?**
1. 쿼리 조건이 인덱스에 포함된 칼럼만으로 해결 가능
- `salary` 와 `department_id` 는 복합 인덱스 `idx_salary_department` 에 포함되어 있습니다.
- 따라서, 인덱스 풀 스캔만으로 데이터를 처리할 수 있으며, 테이블의 데이터 페이지를 읽을 필요가 없습니다.
2. 정렬 작업이 필요 없음
- 인덱스는 항상 정렬된 상태를 유지하므로, 추가적인 정렬 작업 없이 결과를 반환할 수 있습니다.
3. 테이블 풀 스캔보다 디스크 I/O가 적음
- 인덱스 페이지는 테이블 데이터 페이지보다 작으므로, 읽어야 할 디스크 블록 수가 줄어들어 효율적입니다.
- 일반적으로 인덱스 풀 스캔 방식으로 읽는 경우는 “인덱스를 사용하지 못한다” 또는 “인덱스를 효율적으로 사용하지 못한다”라는 표현을 사용한다.
인덱스 레인지 스캔, 인덱스 풀 스캔은 타이트(Tight)인덱스 스캔으로 분류한다.
느슨하게 또는 듬성듬성 인덱스를 읽는 것을 의미한다.
GROUP BY 또는 집합 함수 가운데 MAX() 또는 MIN ()함수에 대해 최적화 하는 경우 사용한다.
테이블 및 인덱스 정보
테이블 dept_emp
에는 dept_no
와 emp_no
라는 두 컬럼이 있습니다.
인덱스 (dept_no, emp_no)
는 dept_no → emp_no 순서로 정렬된 상태이다.
인덱스는 다음과 같이 정렬되어 있다.
dept_no | emp_no
----------------
d001 | 1001
d001 | 1002
d001 | 1003
d002 | 1004
d002 | 1005
d003 | 1006
d004 | 1007
d004 | 1008
SELECT dept_no, MIN(emp_no)
FROM dept_emp
WHERE dept_no BETWEEN 'd002' AND 'd004'
GROUP BY dept_no;
dept_no
가 d002
에서 d004
사이에 있는 데이터만 검색dept_no
그룹에서 emp_no
의 최소값 MIN(emp_no)
를 가져온다.루스 인덱스 스캔은 이미 정렬된 상태임을 활용하여, 각 그룹에서 필요한 첫 번째 레코드만 읽는 방식이다.
동작 과정:
인덱스에서 범위 조건을 만족하는 첫 번째 dept_no
로 이동:
d002
.해당 dept_no
의 첫 번째 레코드만 읽음:
d002 | 1004
→ emp_no = 1004
를 선택.d002
그룹은 스킵하고 다음 그룹으로 이동.다음 dept_no
의 첫 번째 레코드로 이동:
d003 | 1006
→ emp_no = 1006
을 선택.d003
그룹은 스킵하고 다음 그룹으로 이동.마지막 dept_no
의 첫 번째 레코드로 이동:
- d004 | 1007
→ emp_no = 1007
을 선택.
- 나머지 d004
그룹은 스킵.
mysql> ALTER TABLE employees ADD INDEX ix_gender_birthdate(gender, birth_date);
이 인덱스를 사용하려면 WHERE 조건절에 gender 칼럼에 대한 비교 조건이 필수다.
mysql> SELECT * FROM employees WHERE gender='M' AND birth_date >= '1965-02-01'
gender 칼럼과 birth_date 칼럼의 조건을 모두 가진 두 번째 쿼리는 인덱스를 효율적으로 사용할 수 있다.
MySQL 8.0 버전 부터는 복합키에 gender 가 빠지더라도 옵티마이저가 gender 컬럼을 건너뛰어서 birth_date 칼럼만으로 검색이 가능하게 해주는 인덱스 스킵 스캔이 가능하도록 되었다.
이전 버전에서는 gender 가 없으면 인덱스 풀 스캔으로 데이터를 검색한다.
복합 인덱스 (gender, birth_date)
에서 gender 조건 없이 Full Index Scan이 가능하다는 게 무슨 의미인가?
SET optimizer_switch=’skip_scan=on’ 일 경우
mysql> SELECT gender, birth_date FROM employees WHERE gender='M' AND
birth_date >= '1965-02-01';
mysql> SELECT gender ,birth_date FROM employees WHERE gender ='F' AND
birth_date >= '19965-02-01';
이런 형식으로 인덱스 스킨 스캔이 작동한다.
인덱스 스킵 스캔의 단점
WHERE 조건절에 조건이 없는 인덱스의 선행 칼럼의 유니크한 값의 개수가 적어야 한다.
A | B
------------
M | 1
M | 2
F | 3
F | 4
F | 5
SELECT * FROM table WHERE B = 3;
이 쿼리에서 선행 컬럼 A
에 조건이 없더라도, MySQL 은 인덱스 스킵 스캔을 사용하여 다음 단계를 수행한다.
A
의 유니크한 값 (M
, F
)를 하나씩 확인A
값에 대해, B = 3
조건을 검색A
의 유니크 값이 적으면, 옵티마이저는 모든 유니크 값을 순차적으로 탐색해야한다.A
의 유니크 값이 100 개라면, 각 값에 대한 후행 컬럼 B
를 검색하는 작업이 100번 반복된다.A
의 유니크 값이 2개(M
, F
) 뿐이라면, 두 번의 범위 탐색만으로 전체 인덱스를 스캔해야 할 가능성이 높다.쿼리가 인덱스에 존재하는 칼럼만으로 처리 가능해야함(커버링 인덱스)
복합키로 보면됨
선행 키 → 후행 키 순으로 정렬된다.
다중 칼럼 인덱스에서 인덱스 내에서 각 칼럼의 위치(순서)가 상당이 중요하다.
인덱스 정렬은 오름차순, 내림차순
### 3.6.1 인덱스의 정렬
1. 인덱스 스캔 방향
```sql
mysql> SELECT * FROM employees ORDER BY first_name DESC LIMIT 1;
```
- 쿼리의 인덱스에 ASC, DESC 를 통해서 읽기 방향을 인덱스의 오름차순, 내림차순으로 읽을 수 있다.
1. 내림차순 인덱스
- 정순 스캔보다 역순 스캔이 느린 이유 두가지
1. 페이지 잠금이 인덱스 정순 스캔 (Forward index scan)에 적합한 구조
2. 페이지 내에서 인덱스 레코드가 단방향으로만 연결된 구조
- **단방향 연결된 구조에서 역순접근이 가능한 이유**
InnoDB 의 B-Tree 구조는 리프 노드에서 단방향 링크드 리스트로 연결되어 있다.
구조적 특징
- 리프 노드의 레코드들은 다음 레코드로 이동할 수 있다로고 단방향으로 연결되어 있다.
- 예 : 레코드 1 → 2 → 3- > … → N 처럼 오른쪽으로만 이동할 수 있는 구조
- 역순 스캔의 경우, 부모 노드를 통해 다시 이전 리프 노드로 이동해야 한다.
- 역순 스캔에서는 InnoDB가 현재 페이지에서 끝까지 도달한 후, 부모 노드로 돌아가 이전 페이지를 다시 검색하는 과정을 반복해야 한다.
단방향 구조와 역순 스캔
- 단방향 연결은 순차(정순) 스캔에 최적화되어 있으며, 역순 스캔은 효율성이 떨어질 수 밖에 없다.
- 역순 접근은 추가적인 페이지 이동과 복잡한 탐색 과정이 필요하므로 정순 스캔보다 느리게 동작한다.
- **페이지 잠금이 정순 스캔에 적합한 이유**
정순 스캔과 페이지 잠금의 관계
- InnoDB 에서 정순 스캔은 리프 노드의 왼쪽에서 오른쪽으로 순차적으로 진행된다.
- 이 과정에서 InnoDB는 다음 페이지를 읽고 잠금하는 과정이 매우 효율적이다.
- 한 페이지를 스캔한 뒤, 연속적인 페이지로 이동하기 때문에 I/O 및 잠금 관리가 최적화 된다.
역순 스캔의 잠금 비효율성
- 역순 스캔은 이전 페이지로 이동해야 하므로, 한 번 풀었던 잠금을 다시 설정하거나 다음 페이지를 거슬러 올라가는 과정을 반복해야 한다.
- 이러한 방식은 페이지 잠금 및 해제 과정에서 오버헤드가 발생하며, 성능 저하로 이어진다.
인덱스에서 칼럼의 순서와, 칼럼에 사용된 동등 비교(”=”) 아니면 (”<”) or (”>”) 같은 범위 조건인지에 따라 인덱스 칼럼의 활용 조건이 달라진다.
mysql> SELECT * FROM dept_emp WHERE dept_no = 'd002' AND emp_no >= 10114;
위의 쿼리에서 두 가지 인덱스 생성되어있다고 했을때 차이점
케이스 A : INDEX (dept_no, emp_no)
케이스 B : INDEX (emp_no, dept_no)
케이스 A 인덱스에서 2번째 칼럼인 emp_no 는 비교작업을 좁히는데 도움을 주지만, 케이스 B 인덱스에서 2번째 칼럼인 dept_no는 비교 작업의 범위를 좁히는데 아무런 도움을 주지 못하고, 단지 쿼리 조건에 맞는지 검사 용도로만 사용 됐다.
B-Tree 인덱스는 왼쪽 값에 기준해서(Left-most) 오른쪽 값이 정렬돼 있다.
케이스 A : INDEX (first_name)
mysql> SELECT * FROM employees WHERE first_name LIKE '%mer';
케이스 B : INDEX (dept_no, emp_no)
```sql
mysql> SELECT * FROM dept_emp WHERE emp_no >= 10144;
```
- 인덱스가 (dept_no, emp_no) 칼럼 순서대로 생성돼 있다면 인덱스의 선행 칼럼인 dept_no 조건 없이 emp_no 값으로만 검색하면 인덱스를 효율적으로 사용할 수 없다.
- dept_no 칼럼에 대해 먼저 정렬한 후, 다시 emp_no 칼럼값으로 정렬돼 있기 때문이다.
B-Tree 인덱스의 작업 범위 결정 조건으로 사용할 수 없는 경우 (체크 조건으로는 사용할 수 있다.)
다중 칼럼으로 만들어진 인덱스의 사용 조건 여부
INDEX ix_test ( column_1, column_2, column_3, .., column_n)
사용자가 자주 사용할 만한 키워드로 인덱스를 구축한다.
### 5.1.1 어근 분석 알고리즘
- 불용어(Stop Word)처리
- 검색에서 별 가치가 없는 단어를 모두 필터링해서 제거하는 작업
- 어근 분석(Stemming)
- 검색어로 선정된 단어의 뿌리인 원형을 찾는 작업
### 5.1.2 n-gram 알고리즘
- 몇 글자씩 잘라서 인덱싱하는 방법
### 5.1.3 불용어 변경 및 삭제
- MySQL 서버에 내장되어 있는 불용어 대신 사용자가 직접 불용어를 등록하는 방법을 권장한다.
- **전문 검색 인덱스의 불용어 처리 무시하는 방법**
- MySQL 서버의 모든 전문 검색 인덱스에 대해 불용어를 완전히 제거하는 것
- ft_stopword_file=’’공백
- InnoDB 스토리지 엔진을 사용하는 테이블의 전문 검색 인덱스에 대해서만 불용어 처리 무시
- innodb_ft_enable_stopword 시스템 변수 OFF
- **사용자 정의 불용어 사용**
- 불용어 목록을 파일로 저장하고, MySQL 서버 설정 파일에서 파일의 경로를 다음과 같이 ft_stopword_file로 설정
- ft_stopword_file=’/data/my_custom_stopword.txt’
- InnoDB 스토리지 엔진을 사용하는 테이블 전문 검색 엔진에서만 사용할 수 있는데, 불용어의 목록을 테이블로 저장하는 방식이다.
쿼리 문장이 전문 검색을 위한 문법(MATCH … AGAINST …)을 사용
테이블이 전문 검색 대상 칼럼에 대해서 전문 인덱스 보유
mysql > CREATE TABLE tb_test (
doc_id INT,
doc_body TEXT,
PRIMARY KEY (doc_id),
FULLTEXT KEY fx_docbody (doc_body) WITH PARSER ngram
) ENGINE=InnoDB;
mysql> SELECT * FROM tb_test WHERE MATCH (doc_body) AGAINST ('애플' IN BOOLEAN MODE);
mysql> CREATE TABLE user (
user_id BIGINT,
first_name VARCHAR (10),
last_name VARCHAR(10),
PRIMARY KEY (user_id)
);
fisrt_name 과 last_name 을 합쳐서 인덱싱 하기 위해서는 이전에는 full_name 칼럼을 만들고 업데이트를 했어야 했다면
SQL 8.0 버전에서 부터는 가상 칼럼을 추가하는 방식으로 진행한다.
mysql> ALTER TABLE user ADD full_name VARCHAR(30) AS
(CONCAT(first_name, ' ', last_name)) VIRTUAL, ADD INDEX ix_fullname (fullname);
가상 칼럼은 테이블에 새로운 칼럼을 추가하는 것과 같은 효과를 내기 때문에 실제 테이블의 구조가 변경된다는 단점이 있다.
mysql > CREATE TABLE user (
user_id BIGINT,
first_name VARCHAR(10),
last_name VARCHAR(10),
PRIMARY KEY (user_id),
INDEX ix_fullname ((CONCAT(first_name, ' ', last_name)))
);
함수를 직접 사용하는 인덱스는 테이블의 구조를 변경하지 않고 인덱스를 생성해 준다.
함수 기반 인덱스를 제대로 활용하려면 반드시 조건절에 함수 기반 인덱스에 명시된 표현식을 그대로 사용해야 한다.
mysql> SELECT * FROM user WHERE CONCAT(first_name, ' ', last_name)='Matt Lee';
CREATE TABLE user (
user_id BIGINT PRIMARY KEY,
credit_scores JSON,
INDEX idx_credit_scores ((CAST(credit_scores->'$.scores' AS UNSIGNED ARRAY)))
);
INSERT INTO user (user_id, credit_scores)
VALUES (1, '{"scores": [700, 750, 800]}'),
(2, '{"scores": [600, 680]}'),
(3, '{"scores": [720, 810]}');
기존 B-Tree 인덱스라면 JSON 배열에서 첫 번째 값만 인덱싱되었겠지만,
멀티밸류 인덱스에서는 [700, 750, 800]
각각이 인덱스에 개별적으로 저장됨.
프라이머리 키 값이 비슷한 레코드 끼리 묶어서 저장하는 것을 클러스터링 인덱스라고 한다.
프라이머리 키 값에 의해 레코드의 저장위치가 결정된다. 즉 프라이머리 키값이 변경되면 레코드 저장위치도 변경된다.
클러스터링 인덱스 = 클러스터링 테이블, 프라이머리 키 = 클러스터링 키
키 검색이 빠르지만, 저장이나 프라이머리 키 변경은 상대적으로 느리다.
클러스터링 테이블의 리프노드에 레코드 정보가 다 들어가져 있다.
프라이머리 키가 없는 테이블의 경우 프라이머리키의 설정은 어떻게 해야하나?
내부적으로 증가된 클러스터링 키는 실제 쿼리에서 사용할 수 없다.
InnoDB 는 세컨더리 인덱스의 리프 노드에는 클러스터링 인덱스를 가지고 있다.
만약 리프노드에 클러스터링 인덱스가 아닌 레코드의 주소가 저장된다면, 레코드 주소가 변경될때마다. 세컨더리 인덱스의 리프노드의 값을 변경해야하는 불편함이 생긴다.
mysql> CREATE TABLE employees (
emp_no INT NOT NULL,
first_name VARCHAR (20) NOT NULL,
PRIMARY KEY (emp_no),
INDEX ix_firstname (first_name)
);
mysql> SELECT * FROM employees WHERE first_name='Aamer';
MyISAM : ix_firstName 인덱스를 검색해서 레코드의 주소를 확인한 후, 레코드의 주소를 이용해 최종 레코드를 가져옴
InnoDB: ix_firstname 인덱스를 검색해 레코드의 프라이머리 키 값을 확인 후, 프라이머리 키 인덱스를 검색해서 최종 레코드를 가져옴
장점 | 프라이머리 키(클러스터링 키)로 검색할 때 처리 성능이 매우 빠름 (특히, 프라이머리 키를 범위 검색하는 경우 매우 빠름) |
---|---|
테이블의 모든 세컨더리 인덱스가 프라이머리 키를 가지고 있기 때문에 인덱스만으로 처리될 수 있는 경우가 많음(이를 커버링 인덱스라고 한다.) | |
단점 | 테이블의 모든 세컨더리 인덱스가 클러스터링 키를 갖기 떄문에 클러스터링 키 값의 크기가 클 경우 전체적으로 인덱스의 크기가 커진다. |
세컨더리 인덱스를 통해 검색할 때 프라이머리 키로 다시 한번 검색해야 하므로 처리 성능이 느리다. | |
INSERT 할 때 프라이머리 키에 의해 레코드의 저장 위치가 결정되기 때문에 처리 성능이 느리다. | |
프라이머리 키를 변경할 때 레코드를 DELETE 하고 INSERT 하는 작업이 필요하기 때문에 처리 성능이 느리다. |