owned this note
owned this note
Published
Linked with GitHub
# Real MySQL 8.0 1권 스터디
- 대상: 8장 인덱스
- 일시: 2024년 9월 29일 일요일 오후 8시
# 참여자
- 옐리
- 샤피
- 데이
---
# 이해했는지 확인하기
- 한 줄로 설명하지 못하면 이해하지 못한 것으로 생각합시다.
- 이해했다고 착각한 것들 잡아봅시다.
- 이해하지 못한 부분은 스터디 종료 후 반드시 학습합시다.
## 8.1
### 랜덤 I/O 와 순차 I/O 중 더 빠른 것과 그 이유
- 3개의 페이지(3 x 16KB)를 디스크에 기록할 때를 가정
- (hint) 디스크 헤드, 시스템 콜
#### 샤피
- 순차 I/O가 더 빠름. 3개의 페이지를 기록할 때 순차I/O는 시스템 콜을 1번 요청, 랜덤I/O 시스템 콜을 3번 요청.
#### 데이
- 순차 I/O가 더 빠르다 할 수 있다
- 순차는 디스크 헤드 한번만
- 랜덤은 디스크 헤드 세번 움직여야
- 시스템 콜이 세번 되는게 부하가 더 높기 때문이다.
#### 옐리
- 순차 I/O가 더 빠른데, 디스크 헤드가 세 개 페이지 기록할 때 순서대로 쭉 기록하면 되어서 더 빠름
- 시스템 콜이 랜덤 I/O가 3배 더 많이 요구
## 8.2
### WHERE 조건절에 쓰인다고 인덱스를 무작정 생성하면 안되는 이유 간단하게 1줄로 설명
#### 옐리
- 테이블에 인덱스가 많아져 인덱스 크기가 커짐
- 쓰기 작업을 할 때 더 느려질 수 있어 역효과를 불러올 수 있음
#### 샤피 🔄️
- 인덱스를 사용하면 INSERT, UPDATE, DELETE 처리 성능을 희생하고, 조회 성능의 이점을 얻는다
- 인덱스를 많이 생성하면 저장할 때 성능에 문제가 될 수 있다
#### 데이 🥲
- 부가적으로 더 만들어 내야 하는 부분
- 변경 작업에 성능을 희생, 조회하는 데 빠른 검색이라는 이점 취득
- 인덱스가 많아진다고 무조건 좋지 않다
## 8.3
### B-Tree 인덱스에 대해 1줄로 설명
#### 데이
- 인덱스 키와 값 저장하는 형태
- B는 Balanced
- 루트에 여러 자식들 달려있을 수 있는 구조
- 인덱스는 루트와 브랜치 노드에 값이 들어가 있고 마지막 리프 노드에 실제 데이터 레코드를 찾아가기 위한 주소값을 저장해 사용
#### 샤피 🔄️
- 일반적으로 사용되는 알고리즘
- Balanced
- 루트, 브랜치(없을수도 있음), 리프 노드
- 리프 노드에 실질적인 데이터 저장 주소가 들어있음
#### 옐리 🤔
- 트리 구조 자료구조 사용하는 알고리즘
- 루트 노드, 브랜치 노드, 리프 노드로 구성된 알고리즘이고
- 실제 데이터 저장되는 곳은 리프 노드의 인덱스 키와 밸류로 되어 있고 밸류가 PK로 되어 있다
- InnoDB에 있어서는 리프 노드의 PK값으로 다시 한 번 더 B-Tree를 통해서 실제 데이터 레코드가 저장된 주소를 찾는 자료구조.
### 인덱스 키가 추가되는 과정
- 테이블의 레코드가 추가되서 인덱스 키가 추가되는 상황
- (hint) 루트 노드, 브랜치 노드, 리프 노드...
#### 옐리 🤔
- **인덱스 키의 값**에 PK를 저장해야 하는데, 항상 정렬된 값 정렬해야 하므로 루트, 브랜치, 리프를 거쳐 리프 노드 어디에 할지 찾아야
- 찾으면 저장
#### 데이
- 정렬되어 있다는 것이 중요하므로 들어간다 하더라도 루트, 브랜치, 리프가 변경될 수 있는 가능성
- 추가해야 하는데 꽉 찼다면 리프 노드를 하나 더 추가해야
- 인덱스 추가 비용이 많이 든다고 알려짐
#### 샤피
- 적절한 위치에 저장을 해야하는데, 정렬된 상태로 저장되야 하기 때문에 어디에 저장할지 잘 찾아야 함.
- 만약 리프 노드가 꽉 차서 저장할 공간이 없어지면 분리가 되야함.
- 분리 작업은 브랜치, 루트 노드에 까지 영향을 미칠 수 있다.
### 인덱스 키가 삭제되는 과정
- 테이블의 레코드가 삭제되서 인덱스 키가 삭제되는 상황
- (hint) 리프 노드
#### 샤피
- 인덱스 키 삭제는 굉장히 단순한데, 해당 키 값이 포함된(저장된) 리프 노드를 찾아서 삭제 마크를 하면 된다.
#### 데이
- 해당 키 값이 저장된 리프 노드를 찾아서 삭제 마크
- 이 마킹된 공간은 재사용되거나 방치됨
#### 옐리
- 리프 노드를 찾아서 삭제 마킹을 하는데, 이 삭제 마킹을 하는 작업 자체가 디스크 쓰기 작업을 하는 것임
- 쓰기 작업을 하기 때문에 지연 처리가 될 수 있음
### 인덱스 키가 변경되는 과정
- 테이블의 레코드가 변경되서 인덱스 키가 변경되는 상황
#### 데이
- 바로 변경할 수 없고, 삭제하고 다시 생성하는 식으로 해야 변경 가능
#### 옐리 🤔
- 삭제 작업을 먼저 하고, 생성 과정을 다시 하는 식으로 진행이 된다
- 삭제 작업 자체가 쓰기 작업이므로 지연처리가 될 수 있음 => 체인지 버퍼
#### 샤피
- 리프 노드 키 값에 따라서 저장되는 구조 => 변경은 불가능하기 때문에 삭제를 하고 새로 생성하는 방식으로 처리
### 인덱스 키 값이 커지면 디스크 읽기가 늘어나는 이유
- (hint) B-Tree 깊이와 관련
#### 데이
- 인덱스 키가 페이지 안에 저장되는데, 페이지 값은 일반적으로 고정된 값
- 키 값이 커지면 적은 수의 인덱스가 들어가게 되고 더 많은 양이 페이지 요구됨
- 깊이가 깊어지는 만큼 자연스럽게 디스크 읽기가 늘어남
#### 옐리
- 인덱스의 키의 값이 커지면 키도 페이지에 저장되므로 그만큼 페이지에 저장될 수 있는 인덱스 키 값 개수가 줄어듦
- 동일한 레코드 읽는다 할 때 페이지가 더 많이 나눠져 있기 때문에 더 많은 디스크 읽기 작업 처리되어야
- 인덱스 키 값이 늘어나면 그만큼 디스크 읽기가 늘어난다 볼 수 있음
#### 샤피
- 인덱스 키가 페이지에 저장되는데, 페이지 크기는 고정 크기
- 인덱스의 평균 크기가 늘어날수록 저장될 수 있는 개수가 줄어듦
- 그만큼 깊이가 깊어집니다.
- 그래서 인덱스 키 값이 작을수록 좋습니다.
### 선택도(또는 카디널리티)가 낮을수록 인덱스를 통한 검색 효율이 떨어지는 이유
- (hint) 아래 쿼리를 이용해 보세요.
```sql
-- 전체 레코드가 1만건
-- 국가와 도시는 중복되서 저장돼 있지 않음
-- country 컬럼의 유니크 값이 10개 일 때를 가정
SELECT * FROM tb_test
WHERE country='KOREA' AND city='SEOUL';
```
#### 샤피
- country 개수 1000개 중 seoul 은 1건
- 1000건 중 999건은 불필요한 행동
#### 옐리
- 1만 건 중 중복되지 않은 나라는 10개라면 city는 1000개가 될 것
- 1000개 중 1건을 필터링해야 하므로 999개가 불필요한 읽기
#### 데이
- 선택도가 2라면
- 만건 중 첫 번째 where 조건에서 전체의 50퍼
- 5000번을 더 읽어야 하는 상황이 됨
### 1000만건 중 700만건을 조회할 때 인덱스를 사용하지 않는 이유
#### 데이
- 전체 70%
- 인덱스로 테이블 읽는 건 테이블을 읽는 것보다 하나 더 거쳐서 읽는 작업
- 이 경우 인덱스보다 바로 테이블을 읽는 것이 더 효율적
### 인덱스 레인지 스캔
- (hint) 루트 노드에서 시작한다
- (hint) 브랜치 노드를 거친다
- 그 다음엔?
#### 샤피 🔄️
- 인덱스 범위가 결정됐을 때 사용되는 방식
- 루트 노드부터 리프 노드까지 거쳐야 실제 레코드 주소값을 얻을 수 있기 때문에
- 레코드 주소값을 찾고 거기서부터 쭉 읽으면 된다.
### 인덱스 풀 스캔
#### 옐리
- 레인지 스캔과 다르게 모든 인덱스를 처음부터 끝까지 전부 읽는 스캔 방식
- 테이블 풀 스캔보다 빠르다고 알려져 있음
### 루스 인덱스 스캔
```sql
SELECT dept_no, MIN(emp_no)
FROM dept_emp
WHERE dep_no BETWEEN 'd002' AND 'd004'
GROUP BY dept_no;
```
- 위의 쿼리 예시를 사용하세요
- INDEX(dept_no, emp_no) 인덱스가 생성되어 있습니다.
- MIN() 함수가 사용됐습니다.
#### 데이
- 모든 인덱스를 다 읽는 게 아니라 느슨하게 듬성듬성 읽는 방식
- 필요하지 않다 생각되는 부분은 건너뛰고 스캔하게 됨
- GROUP BY로 정렬해서 값을 읽어오고 있는데, d002도 한개만 있지는 않을 것
- 이 중 가장 작은 값을 읽어오므로 그 사이에 있는 가장 작은 값만 읽으면 나머지는 읽을 필요 없음
### 인덱스 스킵 스캔
- (hint) 다중 컬럼 인덱스
#### 샤피 🔄️
- 다중 컬럼 인덱스에서 앞쪽의 컬럼이 WHERE 절에 없을 때 사용된다.
- 선택도가 낮을 때 효과적
- MySQL 8.0 부터 인덱스를 중간에 건너띄면서 수행
### 다중 컬럼 인덱스 내에서 컬럼의 순서가 중요한 이유
#### 옐리 😭
- 인덱스를 읽을 때 인덱스 생성할 때 설정한 칼럼 순서 때문에 중요
- Where 조건을 2번부터 쓰게 된다면 컬럼 1번과 2번에 대한 인덱스 사용 불가. 비효율적
-> 앞 컬럼에 의존해서 정렬 관련 추가
### 다중 컬럼 인덱스를 생성할 때 각 컬럼의 정렬 방향을 설정할 수 있다 없다?
- (mysql 8.0 기준)
#### 데이
- 각자 다르게 정렬 방향 설정 가능
- 이전 버전인 경우는 혼합하더라도 그렇게 설정 안됨. 문법적으로만 가능한 것처럼 보임
### 내림차순 인덱스가 무엇인가요?
#### 샤피
- 큰 값에서 작은 값으로 정렬된 인덱스
### 내림차순 인덱스가 필요한 이유가 무엇인가요?
- `인덱스 역순 스캔`이 `인덱스 정순 스캔`보다 느릴 수 밖에 없는 이유가 뭐가 있을까요?
- (hint) 페이지 잠금
- (hint) 인덱스 레코드가 단방향으로만 연결
#### 옐리
- 인덱스 정순 스캔에 최적화된 구조
- 페이지 잠금되는 순서가 정순으로 되어 있음
- 인덱스 레코드들이 단방향으로만 연결되므로 역방향으로 읽는데 시간이 더 오래 걸림
### B-Tree 인덱스의 효율성
```sql
SELECT * FROM dept_emp
WHERE dept_no='d002' AND emp_no >= 10114;
```
- 작업 범위 결정 조건
- INDEX(dept_no, emp_no) 케이스 일 때를 가정
- 필터링 조건
- INDEX(emp_no, dept_no) 케이스 일 때를 가정
#### 데이
- 첫 번째 조건일때는 두 조건을 만족하는 레코드를 찾고 -> 쭉 스캔하면서 d002가 아닐 때까지 계속 확인
- 두 번째 조건일때는 emp_no를 먼저 확인하고, 그 다음 레코드들에서 d002인 부분을 계속해서 체크
- 첫 번째 방법이 더 효율적인 방법
### B-Tree 인덱스의 가용성
```sql
SELECT * FROM employees
WHERE first_name like '%mer%';
```
- 위의 쿼리에서 인덱스 레인지 스캔 방식으로 검색할 수 없는 이유에 대해 1줄로 설명하세요
#### 샤피
- n 번째 컬럼에 의존하는 n + 1 번째 컬럼 => 왼쪽 컬럼을 기준으로 정렬
- '%'가 왼쪽에 있기 때문에 인덱스 레인지 스캔 방식으로는 검색할 수 없음
## 8.4
### MySQL에서 지원하는 데이터 타입 중 Geometry(기하학적 도형) 정보를 관리할 수 있는 데이터 타입 4가지는?
1. POINT
2. LINE
3. POLYGON
4.(hint) GEOMETRY (나머지 3개 타입의 슈퍼 타입)
### MBR이란?
#### 옐리
- 최소 경계 사각형
- GEOMETRY 타입의 도형이 최소 경계 사각형 안에 포함되어있는지 확인할 때 사용하는 개념
### R-Tree 인덱스란? (B-Tree, MBR 2개를 이용해 설명)
#### 데이
- MBR을 B-Tree 안에 저장하는 식으로 사용하는 인덱스
### R-Tree 인덱스로 할 수 있는 검색
- (hint) WGS84(GPS) 기준 위도, 경도 좌표 저장을 할 수 있다.
- (hint) 현재 사용자 위치 활용
- 정답: 현재 사용자 위치를 활용한 __검색
#### 샤피
- 반경
### 공간 인덱스를 사용할 수 있는 함수 2개
- (hint) ST_Within()
- ST_????????()
#### 옐리
- ST_Contains
---
# 이번 스터디 방식에 대한 소감
- ...
- ...