Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8 - 리브 #46

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions real-my-sql/m0522inju/Ch10-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Chapter 10 실행 계획

> 옵티마이저가 관리자나 사용자 개입 없이 항상 좋은 실행 계획을 만들어낼 수 있는 것은 아니다. 옵티마이저의 문제점을 관리자나 사용자가 보완할 수 있도록 `EXPLAIN` 명령으로 옵티마이저가 수립한 실행 계획을 확인할 수 있게 해준다.

## 10.1 통계 정보

- MySQL 5.7 버전까지 : 테이블, 인덱스 개괄 정보를 가지고 실행 계획 수립
- ➡️ 테이블 칼럼의 값들 분포 정보 ❌
- ➡️ 실행 계획의 정확도가 떨어짐
- MySQL 8.0: 히스토그램(Histogram, 인덱스되지 않은 칼럼들에 대해서도 데이터 분포도를 수집해서 저장) 정보 도입

### 10.1.1 테이블 및 인덱스 통계 정보

- 비용 기반 최적화에서 가장 중요한 것은 통계 정보다.
- MySQL 또한 다른 DBMS와 같이 비용 기반 최적화를 사용하지만 정확도가 높지 않고 통계 정보의 휘발성이 강했다.
- ➡️ 쿼리 실행 계획을 수립할 때 실제 테이블의 데이터를 일부 분석해서 통계 정보를 보완했다.
- ➡️ MySQL 5.6 버전부터 통계 정보의 정확성을 높일 수 있는 방법이 제공되기 시작했다.

#### 10.1.1.1 MySQL 서버의 통계 정보

- MySQL 5.6 버전부터는 InnoDB 스토리지 엔진을 사용하는 테이블에 대한 통계 정보를 **영구적**으로 관리할 수 있게 개선됐다. ➡️ 이전엔 메모리에 관리해서 MySQL 서버 재시작되면 통계 정보가 모두 사라졌음
- 각 테이블의 통계 정보를 `mysql` 데이터베이스의 `innodb_index_stats`, `innodb_table_stats` 테이블로 관리
- ➡️ MySQL 서버 재시작돼도 기존 통계 정보 유지 가능
- 테이블 생성할 때 `STATS_PERSISTENT` 옵션 ➡️ 테이블 단위로 영구적인 통계 정보 보관할지 말지 결정할 수 있다.
- 0 : MySQL 5.5 이전 방식대로 관리
- 1 : 테이블에 저장
- DEFAULT : 테이블 통계 영구 관리할지 말지 Innodb_stats_persistent 시스템 변수 값(기본 : ON(1))으로 결정
- MySQL 5.5 버전까지 테이블 통계 정보가 특정 이벤트가 발생하면 자동으로 갱신됐는데 영구적인 통계 정보가 도입되면서 의도하지 않은 통계 정보 변경을 막을 수 있게 됐다.
- 영구적인 통계 정보를 사용하면 MySQL 서버 점검이나 사용량이 많지 않은 시간을 이용해 더 정확한 통계 정보를 수집할 수도 있다.
- ➡️ `innodb_stats_persistent_sample_pages` 시스템 변수에 높은 값 설정
- ➡️ 너무 높이면 통계 정보 수집 시간이 길어진다.

### 10.1.2 히스토그램

- 칼럼의 데이터 분포도 참조할 수 있다.

#### 10.1.2.1 히스토그램 정보 수집 및 삭제

- 히스토그램 정보는 자동으로 수집되지 않고 `ANALYZE TABLE ... UPDATE HISTOGRAM` 명령을 실행해 수동으로 수집 및 관리된다.
- 수집된 히스토그램 정보는 시스템 딕셔너리에 함께 저장된다.
- MySQL 서버가 시작될 때 딕셔너리의 히스토그램 정보를 `information_schema` 데이터베이스의 `column_statistics` 테이블로 로드한다.
- 실제 히스토그램 정보를 조회하려면 `column_statistics` 테이블을 SELECT해서 참조할 수 있다.
- MySQL 8.0 버전에서는 2종류의 히스토그램 타입이 지원된다.
- **Singleton**
- Value-Based 히스토그램 or 도수 분포
- 컬럼값 개별로 레코트 관계를 관리하는 히스토그램 / 컬럼이 가지는 값별로 버킷이 할당
- 하나의 버킷 = 컬럼 값 + 발생 빈도 비율 2개 값 가짐
- 유니크한 값의 개수가 상대적으로 적을 때 사용
- **Equi-Height(높이 균형)**
- Height-Balanced 히스토그램
- 컬럼값의 범위를 균등한 개수로 구분해서 관리 / 개수가 균등한 컬럼값 범위별로 하나의 버킷 할당
- 하나의 버킷 = 범위 시작 값 + 마지막 값 + 발생 빈도율 + 유니크한 값의 개수 4개 값 가짐
- 버킷(Bucket) 단위로 구분되어 레코드 건수나 컬럼값의 범위가 관리된다.
- `information_schema.column_statistics`의 `HISTOGRAM` 칼럼 필드 의미는 책 403쪽 참고하기

- 생성된 히스토그램 삭제도 가능하지만 테이블 데이터를 참조하는 게 아니라 딕셔너리 내용만 삭제하기 때문에 쿼리 처리 성능에 영향을 주지 않고 즉시 완료된다.
- 🚨 히스토그램이 사라지면 쿼리 실행 계획이 달라질 수 있다.
- 히스토그램을 삭제하지 않고 MySQL 옵티마이저가 히스토그램을 사용하지 않게 하려면 `optimizer_switch` 시스템 변수 값을 변경하면 된다.
- 특정 커넥션 또는 특정 쿼리에서만 히스토그램을 사용하지 않게 할 수도 있다.
- 방법은... 책 403 - 404

#### 10.1.2.2 히스토그램의 용도

- 기존 MySQL 서버가 가지고 있던 통계 정보는 테이블 전체 레코드 건수와 인덱스된 칼럼이 가지는 유니크한 값의 개수 정도였다.
- 히스토그램 정보가 없으면 옵티마이저는 데이터가 균등하게 분포돼 있을 것으로 예측한다.
- 히스토그램 특정 컬럼이 가지는 모든 값에 대한 분포도 정보를 가지지는 않지만 각 범위(버킷)별 레코드 건수와 유니크한 값의 개수 정보를 가지기 때문에 훨씬 정확한 예측을 할 수 있다.
- 쿼리의 성능에 상당한 영향을 미칠 수 있다.
- 조인시 적은 테이블 먼저 읽어서 필터링 ➡️ 큰 테이블
- 쿼리 10배 정도 성능 차이를 보일 수 있다.
- InnoDB 버퍼 풀에 데이터 존재하지 않아서 디스크에서 데이터를 읽어야하면 몇 배 차이 발생 가능
- 각 컬럼에 대해 히스토그램 정보가 있으면 어느 테이블을 먼저 읽어야 조인의 횟수를 줄일 수 있을지 옵티마이저가 더 정확히 판단할 수 있다.

#### 10.1.2.3 히스토그램과 인덱스

- MySQL 서버에서 인덱스는 부족한 통계 정보를 수집하기 위해 사용된다는 측면에서 어느 정도 공통점을 가진다.
- 쿼리의 실행 계획을 수립할 때 사용 가능한 인덱스로부터 조건절에 일치하는 레코드 건수를 대략 파악하고 최종적으로 가장 나은 실행 계획을 선택한다.
- 인덱스 다이브(Index Dive) : 조건절에 일치하는 레코드 건수를 예측하기 위해 옵티마이저는 실제 인덱스의 B-Tree를 샘플링해서 살펴본다.
- 인덱스된 컬럼을 검색 조건으로 사용하는 경우 컬럼의 히스토그램은 사용하지 않고 실제 인덱스 다이브를 통해 직접 수집한 정보를 활용한다.
- 실제 검색 조건의 대상 값에 대한 샘플링을 실행하는 것이므로 항상 히스토그램보다 정확한 결과를 기대할 수 있기 때문이다.
- MySQL 8.0 버전에서 히스토그램은 주로 인덱스되지 않은 컬럼에 대한 데이터 분포도를 `참조`하는 용도로 사용된다.
- 인덱스 다이브 작업은 어느 정도 비용이 필요하며 때로는 (IN 절에 값이 많이 명시된 경우) 실행 계획 수립만으로도 상당한 인덱스 다이브를 실행하고 비용도 그만큼 커진다.

### 10.1.3 코스트 모델(Cost Model)

- MySQL 서버 쿼리 처리 시 수행하는 작업
- 디스크로부터 데이터 페이지 읽기
- 메모리(InnoDB 버퍼 풀)로부터 데이터 페이지 읽기
- 인덱스 키 비교
- 레코드 평가
- 메모리 임시 테이블 작업
- 디스크 임시 테이블 작업
- MySQL 서버는 사용자의 쿼리에 대해 다양한 작업이 얼마나 필요한지 예측하고 전체 작업 비용을 계산한 결과를 바탕으로 최적의 실행 계획을 찾는다.
- 전체 쿼리 비용 계산하는 데 필요한 단위 작업들의 비용 ➡️ 코스트 모델(Cost Model)
- MySQL 서버의 소스 코드에 상수화돼 있던 각 단위 작업 비용을 DBMS 관리자가 조정할 수 있게 개선됐다.
- 설정값은 책 408 - 409...
- 각 단위 작업에 설정되는 비용 값이 커지면 어떤 실행 계획들이 고비용으로 바뀌고 어떤 실행 계획들이 저비용으로 바뀌는지 파악하는 것이 중요하다.
<img width="779" alt="image" src="https://github.com/user-attachments/assets/11625dd7-bc28-4c08-af20-b6c5c532bcbe">

## 10.2 실행 계획 확인

- 실행 계획은 DESC 또는 EXPLAIN 명령으로 확인할 수 있다.
- MySQL 8.0 버전부터 EXPLAIN 명령에 사용할 수 있는 새로운 옵션이 추가됐다.

### 10.2.1 실행 계획 출력 포맷

- FORMAT 옵션을 사용해 실행 계획 표시 방법을 JSON이나 TREE, 단순 테이블 형태로 선택할 수 있다.
- ex) EXPLAIN FORMAT=TREE
- 아무것도 안 적으면 Table

### 10.2.2 쿼리의 실행 시간 확인

- `EXPLAIN ANALYZE` : 쿼리의 실행 계획과 단계별 소요된 시간 정보 확인
- 들여쓰기 : 호출 순서
- 들여쓰기가 같은 레벨에서는 상단에 위치한 라인이 먼저 실행
- 들여쓰기가 다른 레벨에서는 가장 안쪽에 위치한 라인이 먼저 실행
- 소요된 시간(actual time)과 처리한 레코드 건수(rows), 반복 횟수(loop)가 표시된다.
<img width="766" alt="image" src="https://github.com/user-attachments/assets/9670e0de-d82a-467b-a07c-5f24d7f4fd82">

- 실행 계획만 추출하는 것이 아니라 실제 쿼리를 실행하고 사용된 실행 계획과 소요된 시간을 보여준다.
- 실행 계획이 아주 나쁜 경우라면 EXPLAIN 명령으로 먼저 실행 계획만 확인해서 어느 정도 튜닝한 후 EXPLAIN ANALYZE 명령을 실행하는 것이 좋다.