---
# System prepended metadata

title: 10 실행 계획

---

  

# 10 실행 계획

  

## 10.1 통계 정보
- MySQL 8.0 부터는 히스토그램 도입
- MySQL 5.7 버전에서 지원했던 테이블과 인덱스에 대한 개괄적인 정보 + 데이터 칼럼의 분포값 정보 -> 더 정확한 실행계획 선택 가능
### 10.1.1 테이블 및 인덱스 통계 정보
#### MySQL 서버의 통계 정보
- 휘발성 VS 영구성
	- MySQL 5.6 이전 : 휘발성
		- 메모리에만 관리
		- SHOW INDEX 명령으로만 테이블의 인덱스 칼럼 분포도를 확인할 수 있었음
	- MySQL 5.6 부터 : 영구성
		- InnoDB 스토리지 엔진 사용하는 테이블에 대한 통계 정보를 영구적으로 관리할 수 있음
		- mysql 데이터베이스의 innodb_index_stats 테이블과 innodb_table_stats 테이블로 관리 가능
		- 핵심 : 이제 서버 재시작해도 기존 통계 정보 유지할 수 있게 되었다
		- `STATS_PERSISTENT` 옵션 : 영구적으로 통계 정보를 보관할지 결정
- 통계 정보 갱신 (398p)
	- MySQL 5.6 이전 : 자동 갱신
		- 통계 정보가 갱신되면 쿼리 실행 계획 갑자기 달라질 수 있음
		- 통계정보를 바탕으로 실행계획을 수립하기 때문 MySQL 서버가 갑자기 실행 계획을 다르게 설정(예측 불허)
	- MySQL 5.6 부터 : 수동 갱신 가능 + 자동일지도 선택가능
	   - 통계 정보 자동 수집 여부 결정 가능 by `STATS_AUTO_RECALC` 옵션
	   - 통계 정보 수집 시, 몇 개의 페이지 샘플링 할 것인지도 설정 가능 by 시스템 변수
	       - 갯수 늘리기 -> (+) 더 정확한 통계 정보 수집 가능 -> (-) 수집 시간 길어짐
		   - `innodb_stats_transient_sample_pages` : 8 기본 / 샘플링 분석 후 자동 통계 정보로 활용
		   - `innodb_stats_persistent_sample_pages` : 20 기본 / ANALYZE 명령 실행 후, 샘플링 분석 후 통계 정보 테이블에 저장 + 활용

### 10.1.2 히스토그램
- MySQL 5.7 버전까지는
	- 인덱스된 컬럼의 유니크한 값 개수 + 실제 인덱스의 일부 페이지 랜덤 get
- MySQL 8.0 버전부터는 히스토그램 정보 활용 가능

#### 히스토그램 정보 수집 및 삭제
- 수집
	- 히스토그램 정보는 컬럼 단위로 관리
	- 수동 수집 - by `ANALYZE TABLE ... UPDATE HISTOGRAM`
	- (평소에는) 시스템 딕셔너리에 저장
	- (MySQL 서버 실행시) information_schema 데이터베이스, column_statistics 테이블로 로드해서 사용
- 히스토그램 타입 (401p)
    - 싱글톤 히스토그램 : 칼럼값 개별 레코드 건수 기준
        - 버킷 단위는 칼럼이 가지는 값
        - 유니크한 값의 개수가 적은 경우 사용 -> 레인지 스캔인지 확인하는데 유용하지 않을까
    - 높이 균형 히스토그램 : 컬럼값의 범위를 균등한 개수로 구분해서 관리
        - 버킷 단위는 개수가 균등한 컬럼값의 범위
        - (K) 기울기가 비슷하다 -> 각 범위가 비슷한 갯수를 가진다
        - 비슷한 값들의 범위가 좁다 -> 밀도가 낮다
- [주의] 403 p 주의 참고 : 히스토그램 수집 시, 풀스캔 조심 (MySQL 8.0.19 미만의 경우)
- 삭제
   - 쿼리 처리의 성능에 영향 X -> 테이블 데이터를 참조하지 않고 딕셔너리 내용만 삭제하므로
   - 실행 계획에 영향 O -> 실행 계획 수립시 사용하던 히스토그램이 삭제되었으므로
      - MySQL 옵티마이저가 히스토그램 사용하지 않게하는 옵션 사용해서 삭제 시 발생하는 실행 계획 변경에 미리 대비는 가능 (특정 커넥션/쿼리 등 범위를 한정지어서 설정도 가능)  

- 히스토그램의 용도
	- (before) 테이블과 인덱스에 대한 통계정보만으로 실행계획 수립
	- (after) 테이블과 인덱스에 대한 통계정보 + 히스토그램의 분포도 정보
	    - (한 번 더 리마인드 > 히스토그램도 추정치임 특정 페이지만을 샘플링한 정보)
	    - 실행 계획 정확도 up (405p 쿼리 참고)
	    - 조인 순서 결정에 영향 -> 어느 테이블을 먼저 읽어야 조인 횟수를 줄일 수 있는지 정확한 판단 가능  (406p 쿼리 참고)

- 히스토그램 & 인덱스
  - 인덱스 다이브 - 실제 인덱스의 B-tree를 샘플링해서 본다 
       - 인덱스 다이브 통해 사용 가능한 인덱스들을 보고, 조건절에 일치하는 레코드 건수를 대략적으로 파악하는데 사용
 - 인덱스 다이브는 언제 사용?
	 - MySQL 8.0 서버부터는 인덱스된 컬럼을 검색 조건으로 사용하는 경우, 히스토그램이 아닌 인덱스 다이브를 활용 
	 - 즉, 히스토그램은 인덱스되지 않은 칼럼에 대해서만 사용한다
 - 인덱스 다이브의 단점?
	 - 비용 
		 - 특히 IN절이 늘어나면 실행 계획 수립만으로도 인덱스 다이브 실행 비용 크다
		 - (우디 추측) 인덱스 다이브는 그때그때 ~~만들어지나보다~~ 만들어지는 개념이 아니라 인덱스 몇 개만 샘플로 가져와 통계 자료로 활용하는 느낌
  
### 10.1.3 코스트 모델(Cost Model)
- 코스트 모델(Cost Model) : 전체 쿼리의 비용을 계산하는데 필요한 단위 작업들의 비용
- MySQL 5.7 버전까지는
	- MySQL 서버 소스코드에 상수화
	- 하드웨어 따라 달라질 수 있는 점은 고려 X
-  MySQL 5.7 이후부터는
	- 각 단위 작업의 비용을 서버 밖에서 관리 (DB 관리자가 컨트롤 가능)
 - 409 페이지 참고, 코스트 모델의 어떤 단위 작업들에 대해서 비용 설정 가능한지 표기되어있음
    - 작업 비용 높게 산정하면 그만큼 해당 단위 작업들에 쓰는 비용 증가 
 
## 10.2 실행 계획 확인

### 10.2.1 실행 계획 출력 포맷
-  MySQL 8.0 이후부터는, 
	- 한 번의 EXPLAIN 명령으로 PARTITIONS, EXTEND 다 조회 가능
		- PARTITIONS 과 EXTEND 옵션은 문법에서 안녕~
	-  FORMAT 옵션
		- 실행 계획 표시 방법을 선택 가능 - JSON, TREE

### 10.2.2 쿼리의 실행 시간 확인
- `EXPLAIN ANALYZE` 기능
	- 쿼리 실행 계획 확인
	- 실행 단계별로 소요된 시간 정보 확인 가능
	- `EXPLAIN`과 다르다 : `EXPLAIN ANALYZE`는 실제 쿼리를 실행하고 사용된 실행계획과 소요된 시간을 보여줌
- 실행 순서 기준
	- 들여쓰기가 같은 레벨 -> 상단에 위치한 라인 먼저
	- 들여쓰기 다른 레벨 -> 가장 안쪽 라인 먼저
 - `EXPLAIN ANALYZE`  요소 ([공식 문서 참고](https://dev.mysql.com/blog-archive/mysql-explain-analyze/#:~:text=What%20is%20it%3F,points%20in%20the%20execution%20plan.))
   - actual time : {첫 번째 레코드 검색하는데 걸린 시간}...{마지막 레코드 검색하는데 걸린 시간}
   - rows :  처리한(읽은) 레코드 갯수
   - loops : 작업이 반복된 횟수
- 414p 예시 함께 잠깐 보기
  - (우디 자문자답) 왜 F가 가장 먼저 실행되는게 아닐까
  - F 구문이 D에 종속적이라서 그런 듯하다

## 10.3 실행 계획 분석
- focus on
	- 실행 계획이 어떤 접근 방법을 사용해서 어떤 최적화 수행하는지
	- 어떤 인덱스 사용하는지

- EXPLAIN 테이블
	- 표의 각 라인(레코드)
		- 쿼리 문장에서 사용된 테이블 (+ 임시테이블도 포함) 출력
	- 실행 순서대로 출력 (UNION, 상관 서브 쿼리 제외)
		- (+ 상관 서브쿼리 : 내부 쿼리의 값이 결정되는데 외부 쿼리에 의존하는 쿼리)
	- 위일 수록 outer 혹은 먼저 접근한 테이블, 아래쪽일 수록 inner 혹은 나중에 접근한 테이블

### 10.3.1 id 칼럼
- 단위 SELECT 쿼리별로 부여되는 식별자
	- (+ 단위 쿼리 = SELECT 키워드 단위로 구분한 쿼리)
- 여러 개의 레코드가 같은 id 가질 수 있다
	- 하나의 SELECT 문장 내 여러 개의 테이블을 조인하면, 조인되는 테이블 개수만큼 실행 계획 레코드가 출력, BUT 같은 id 값 가짐
- id 칼럼 != 테이블 접근 순서

### 10.3.2 select_type 칼럼
- 단위 SELECT 쿼리가 어떤 타입의 쿼리인지 표시됨
#### Simple
- UNION이나 서브 쿼리 사용하지 않는 단순 SELECT 쿼리
- 일반적, 가장 바깥 SELECT 쿼리
#### Primary
- UNION이나 서브 쿼리 사용하는 SELECT 쿼리의 실행계획에서 가장 Outer 단위 쿼리
#### Union
- UNION으로 결합하는 단위 SELECT 쿼리 중 
	- 첫 번째 : DERIVED
	- 첫 번째 이후 단위 쿼리 : UNION
- (우디) Union 키워드 사용 시, 내부적으로는 임시 테이블 사용하는 구나
#### Dependent Union
- 집합을 결합하는 단위 쿼리(UNION, UNION ALL)에서 외부 쿼리에 의해 영향을 받을 경우 
- 상관 서브쿼리일 경우, DEPENDENT 키워드 표시
#### Union Result
- UNION의 결과를 담아두는 임시 테이블
- 단위 쿼리가 아니므로 별도 id 값은 X
- <union 1, 2> : id 값 1와 2 단위쿼리 결과를 조합
	- MySQL 8.0 이전 : UNION ALL 혹은 UNION 쿼리는 결합 결과를 모두 임시 테이블로 생성
	- MySQL 8.0 이후 : UNION ALL 사용하지 않도록 변경됨 / UNION 여전히 임시 테이블 생성

#### Subquery
> - 위치에 따른 서브 쿼리 명
>     - 중첩된 쿼리 : SELECT 절
>     - 서브쿼리 : WHERE 절
>     - 파생 테이블 : FROM 절 (= 인라인뷰 = 서브 셀렉트)
>  - 반환 값 개수
>      - 스칼라 서브쿼리 : 컬럼 하나인 레코드 1건의 값 1개만 반환
>      - 로우 서브쿼리 :  컬럼 개수와 상관없이 하나의 레코드만 반환
- 여기서의 SUBQUERY는 FROM절 이외에서 사용되는 서브쿼리만을 말한다
- (FROM절에서 사용되는 서브쿼리 = DREIVED 표시)
#### Dependent Subquery
- 서브쿼리가 Outer SELECT 쿼리에서 정의된 칼럼을 사용하는 경우
- 일반적인 서브쿼리보다 처리속도 느리다
	- 외부 쿼리 먼저 수행된 후 내부 쿼리 실행되어야 하므로
#### Derived
- MySQL 5.6 이전
	- 서브쿼리 FROM 절 -> 임시 테이블 만든다 -> DERIVED 실행 계획
- MySQL 5.6 이후
	- FROM 절의 서브쿼리를 외부 쿼리와 통합하는 식의 최적화로 인해 반드시 DERIVED 실행 계획인건 아니게 됨
	- 임시 테이블에서도 인덱스 추가해서 만들 수 있게 됨 (옵티마이저 옵션별로 상이)
- (우디) 서브쿼리 있으면 최대한 조인으로 풀어보자고 또 한 번 다짐..
#### Dependent Derived
- FROM 절 서브쿼리에서 외부 칼럼 사용할 경우
- MySQL 8.0 이전
	- FROM 절 서브쿼리에서 외부 칼럼 사용 X
- MySQL 8.0 이후
	- lateral join을 사용하면 가능
- [lateral join 설명 링크](https://stackoverflow.com/a/28551339)
- Q : one to many -> many 제한

#### Uncacheable Subquery
- 캐시를 사용할 수 없는 서브쿼리를 사용하는 경우
- 원래는 이전의 실행 결과를 그대로 사용할 수 있도록 서브 쿼리 결과를 내부 캐시에 저장
	- 서브쿼리 캐시 != 쿼리 캐시 혹은 파생 테이블
- 캐시 사용 방법
	- SUBQUERY : 한 번 실행에 대한 결과를 캐시하고, 필요할 때 사용
	- DEPENDENT SUBQUERY : 의존하는 바깥 쿼리의 칼럼 값 단위로 캐시해둔다 (?? 정확한 과정은 잘 모르겠어욥)
- 캐시 사용하지 못하게 하는 요소들 3가지 (428p)

#### Uncacheable Union
- 캐시를 사용할 수 없는 유니언 쿼리 사용하는 경우
#### Materialized
- MySQL 5.7 이전
	- outer 테이블 레코드마다 서브 쿼리 실행
- MySQL 5.7 이후
	- FROM절 혹은 IN(subquery) 형태의 쿼리에 대해서, 서브쿼리 내용을 임시 테이블로 구체화한 뒤 outer 테이블과 조인하는 형태로 최적화
- Derived 와 동일하게 작동, 서브쿼리의 위치가 다른 것일 뿐임

### 10.3.3 table 칼럼
- MySQL 서버의 실행 계획은 테이블 기준
- <> 표시는 임시 테이블을 의미, 안의 숫자는 단위 SELECT 쿼리 id 값을 지칭

### 10.3.4 partitions 칼럼
- MySQL 8.0 버전부터는 EXPLAIN 명령으로 파티션 관련 실행 계획까지 한 번에 확인 가능
- 파티션 프루닝 : 파티션이 여러 개인 테이블에서 불필요한 파티션을 빼고 쿼리를 수행하도록, 테이블을 골라내는 과정
- 옵티마이저에서는 쿼리의 조건들을 확인하고, 필요한 파티션만 접근하는 파티션 프루닝으로 쿼리 최적화 시도
   - (+) 대부분 RDB에서 지원하는 파티션은 물리적으로 별도의 공간을 가진다 (그러니, 실행 계획 상으로는 type ALL로 뜰 수 있으니 놀라지 말 것. 우리들이 생각하는 풀스캔 타는거 아님)

### 10.3.5 type 칼럼
- 인덱스를 효율적으로 사용하는지 확인할 수 있는 컬럼
- type 컬럼 == 조인 타입... 이라고 되어있지만 '각 테이블의 접근 방법'이라고 생각해도 좋음

#### system
- 레코드가 1건 혹은 아예 존재하지 않는 테이블 참조
- MyISAM 혹은 MEMORY 테이블에서만 사용

#### const
- 프라이머리 키 혹은 유니크 키 칼럼을 이용하는 WHERE 조건절을 가지고 있고 + 반드시 1건을 반환하는 쿼리만을 처리
- 유니크 인덱스 스캔이라고도 함
- 일부 칼럼만 조건을 사용할 때는 X -> const가 아닌 ref
- 옵티마이저의 쿼리 최적화 단계에서 const인 실행 계획은 먼저 쿼리를 실행해서 통째로 상수화한다 (우디 : 그래서 반드시 1건을 반환해야하는군!)

#### eq_ref
- 여러 테이블이 조인되는 쿼리 실행 계획에서만 표시
- 첫 번째 테이블 컬럼값을 두 번째 조인되는 테이블의 PK 혹은 유니크 키 컬럼의 검색조건(조인 조건)으로 사용할 때 
	- 두 번재 조인 테이블의 type 컬럼 eq_ref
	- 유니크 키로 검색할 경우 NOT NULL이어야 함
	- 조인에서 두 번째 이후에 읽는 테이블에서 반드시 1건만 존재한다는 보장이 있어야 함

#### ref
- 조인의 순서와 관계 없이 사용 + PK 혹은 유니크 키 제약 조건도 X
- 반드시 1건이라는 보장이 없음 (eq_ref가 더 빠름)
- 동등 조건으로만 비교하기 때문에 여전히 빠름

> const, eq_ref, ref
>  - 동등 비교 연산자 사용
>  - 쿼리 튜닝시 나오면 좋은 type

#### fulltext
- MySQL 서버의 전문 검색 인덱스를 사용해 레코드를 읽는 접근 방식
	- 전문 검색 인덱스는 통계정보 관리 X
	- `MATCH (..) AGANIST (..)` 구문으로 전문 검색 실행

#### ref_or_null
- ref 접근 방법 + null 비교 추가
- 나쁘지는 않다 (ㅋㅋ)

#### unique_subquery
- Where 조건절에서 사용되는 IN(서브 쿼리)를 위한 접근 방식
- 서브쿼리에서 중복되지 않는 유니크한 값만 반환할 때 

#### index_subquery
- IN(서브 쿼리) 결과에서 중복된 값이 발생했을 때, 인덱스로 중복된 값을 제거할 수 있음

#### range
- 인덱스 레인지 스캔 형태의 접근 방법
- 범위 형태로 검색하는 경우 적용 (< >, is null, between, in, like)

> 인덱스를 효율적으로 사용한다
> - const, ref, range 사용한다
> (우디 질문 : const는 왜 레인지 스캔일까)

#### index_merge
- 2개 이상의 인덱스를 이용 -> 검색 결과 각각 만듦 -> 병합
- (-) 여러 인덱스 읽어야 함
- (-) 전문 검색 인덱스 사용 쿼리는 적용 안됨
- (-) 검색 결과를 모아주는 비용이 필요 (교집합, 합집합, 중복제거 등등)

#### index 
- 인덱스 풀 스캔
- 비교 레코드 건수는 테이블 풀 스캔과 동일, BUT 데이터 파일 크기가 다름

#### ALL
- 풀 테이블 스캔
- 가장 비효율적인 방법
- '쿼리 튜닝을 한다'는 것이 반드시 인덱스 혹은 테이블 풀 스캔을 사용하지 못하게 하는건 아님
  - 배치 프로그램으로 데이터를 일괄 읽어들여야 하는 상황이나, 

### 10.3.6 possible_keys 칼럼

### 10.3.7 key 칼럼

### 10.3.8 key_len 칼럼

### 10.3.9 ref 칼럼

### 10.3.10 rows 칼럼

### 10.3.11 filtered 칼럼