Real MySQL 8.0 읽으면서 배웠던 부분들을 정리한 글입니다.
실행 계획이란?
옵티마이저가 쿼리를 실행하기 전 쿼리와 통계정보를 통해 세운 최적의 계획
통계 정보
MySQL 5.7 버전까지는 테이블과 인덱스에 대한 개괄적인 정보만을 가지고 실행 계획을 수립했다. MySQL 8.0 버전부터는 인덱스되지 않은 컬럼들에 대해서도 데이터 분포도를 수집해서 저장하는 히스토그램 정보가 도입되었다.
테이블 및 인덱스 통계 정보
주어진 쿼리의 자체의 가장 최적을 선택하는 질의 기반 최적화 방식과 비교하여, 비용 기반 최적화에서 가장 중요한 것은 통계 정보이다.
MySQL 서버의 통계 정보
MySQL 5.5 버전까지는 각 테이블의 통계 정보가 메모리에 관리되었기 때문에, 서버 재시작시 수집된 통계 정보가 모두 사라졌다. 하지만 MySQL 5.6 버전 부터는 innodb_index_stats 테이블과 innodb_table_stats 테이블을 통해 관리할 수 있도록 개선되었다.
- STATS_PERSISTENT: 통계 테이블 영구 저장 여부
- STATS_AUTO_RECALC: 테이블의 통계 정보를 자동 수집 여부
- STATS_PERSISTENT_SAMPLE_PAGES, STATS_TRANSITENT_SAMPLE_PAGES: 샘플링 데이터 블록 개수
히스토그램
히스토그램 정보 수집 및 삭제
히스토그램 정보는 컬럼 단위로 관리 되며, 자동으로 수집되지 않고 ANALYZE TABLE … UPDATE HISTOGRAM 명령을 실행해 수동으로 수집 및 관리된다.
히스토그램 수집 및 관리 과정
- 수집된 히스토그램과 시스템 딕셔너리 저장
- 서버 시작시 딕셔너리의 히스토그램 정보를 information_schema 데이터베이스의 column_statistics 테이블로 로드
- column_statistics 테이블 조회를 통해 실제 히스토그램 정보 조회
히스토그램 타입
히스토그램은 버킷 단위로 구분되어 레코드 건수나 컬럼 값의 범위 관리
- Singleton: 컬럼 값 개별로 레코드 건수를 관리. 각 버킷이 컬럼의 값과 발생 빈도의 비율 2개의 값을 가진다. Value-Based 또는 도수 분포라고도 불림
- Equi-Height: 컬럼 값의 범위를 균등한 개수로 구분해서 관리. 버킷이 범위 시작 값과 마지막 값, 발생 빈도율과 각 버킷에 포함된 유니크한 값의 개수 등 4개의 값을 가진다. Height-Balanced 라고도 불림
information_schema.column_statistics
- sampling-rate: 히스토그램 정보를 수집하기 위해 스캔한 페이지의 비율
- histogram-type: 히스토그램의 종류
- number-of-buckets-specified: 히스토그램을 생성할 때 설정했던 버킷의 개수. 디폴트는 100이며 최대 1024개까지 설정 가능
히스토그램의 용도
히스토그램은 실행 계획의 rows와 filtered를 통한 예측에 사용된다. rows는 인덱스 혹은 풀스캔을 실행할 레코드의 개수이고, filtered는 조건절을 이용하여 옵티마이저가 예측한 스캔할 데이터 중 조건절을 만족하는 데이터 비율 정보이다.
히스토그램과 인덱스
옵티마이저는 조건절에 일치하는 레코드 건수를 예측하기 위해 실제 인덱스의 B-Tree를 샘플링하여 살펴보는데, 이를 Index Dive라고 한다. 히스토그램은 주로 인덱스되지 않은 컬럼에 대한 데이터 분포도를 참조하는 용도로 사용된다.
코스트 모델(Cost model)
전체 쿼리의 비용을 계산하는 데 필요한 단위 작업들의 비용을 코스트 모델이라고 한다.
MySQL 서버에서 처리하는 쿼리 작업들
- 디스크로부터 데이터 페이지 읽기
- 메모리(InnoDB 버퍼 풀)로부터 데이터 페이지 읽기
- 인덱스 키 비교
- 레코드 평가
- 메모리 임시 테이블 작업
- 디스크 임시 테이블 작업
코스트 모델 테이블 타입
- server_cost: 인덱스를 찾고 레코드를 비교하고 임시 테이블 처리에 대한 비용 관리
- engine_cost: 레코드를 가진 데이터 페이지를 가져오는 데 필요한 비용 관리
코스트 모델 공통 컬럼
- cost_name: 코스트 모델의 각 단위 작업
- default_value: 각 단위 작업의 비용
- cost_value: DBMS 관리자가 설정한 값 (사용하는 하드웨어에 따라 비용이 달라질 수 있기 때문에 적용)
- last_updated: 단위 작업의 비용이 변경된 시점
- comment: 비용에 대한 추가 설명
engine_cost 추가 컬럼
- engine_name: 비용이 적용된 스토리지 엔진
- device_type: 디스크 타입
코스트 모델이 실행 계획에 미치는 영향
- key_compare_cost 증가: 정렬을 수행하지 않는 방향의 실행 계획 선택
- row_evaludate_cost 증가: 풀 스캔을 실행하는 쿼리들의 비용 증가, 레인지 스캔 선택할 가능성이 높아짐
- disk_temptable_create_cost, disk_temptable_row_cost 증가: 임시 테이블을 만들지 않는 방향 선택
- memory_temptable_create_cost, memory_temptable_row_cost 증가: 메모리 임시 테이블을 만들지 않는 방향 선택
- io_block_read_cost 증가: InnoDB 버퍼 풀에 데이터 페이지가 많이 적재되어 있는 인덱스를 사용하는 방향 선택
- memory_block_read_cost: InnoDB 버퍼 풀에 적재된 데이터 페이지가 적다고 하더라도 그 인덱스를 사용하는 방향 선택
실행 계획 확인
실행 계획 출력 포맷
table 형태, json 형태, tree 형태로 표시 가능
쿼리의 실행 시간 확인
EXPLAIN ALAYZE 명령을 통해 실행 계획의 단계별로 소요된 시간 정보를 확인할 수 있다.
- actual time: 실제 소요된 시간
- rows: 처리한 레코드 건수
- loops: 반복 횟수
실행 계획 분석
id 컬럼
단위(SELECT) 쿼리마다 다른 id 값이 부여된다. id 컬럼이 테이블 접근 순서를 의미하진 않는다.
select_type 컬럼
단위(SELECT) 쿼리의 타입을 표시
SIMPLE
UNION 이나 서브쿼리를 가지지 않는 SELECT 쿼리
실행계획에서 하나만 존재
실행 계획에서 가장 바깥쪽에 있는 단위 쿼리
PRIMARY
UNION 이나 서브쿼리를 가지는 SELECT 쿼리
실행계획에서 하나만 존재
실행 계획에서 가장 바깥쪽에 있는 단위 쿼리
UNION
UNION으로 결합하는 단위 SELECT 쿼리 중 첫 번째가 아닌 쿼리(첫 번째는 DERIVED)
DEPENDENT UNION
외부 쿼리에 의해 영향 받는 UNION으로 결합된 쿼리
UNION RESULT
UNION 결과를 담아주는 테이블
MySQL 8.0 버전부터는 UNION ALL의 경우 임시 테이블을 사용하지 않도록 기능이 개선되었다.
실제 쿼리에서 단위 쿼리가 아니기 때문에 별도의 id 값은 부여되지 않는다.
SUBQUERY
select_type에서의 SUBQUERY는 FROM 절 외에서 사용되는 서브쿼리를 의미한다. (FROM절은 DERIVED)
DEPENDENT SUBQUERY
바깥쪽(Outer) 쿼리 컬럼에 의존적인 안쪽(Inner)의 서브쿼리
외부 쿼리가 수행된 후 내부쿼리가 수행되어야 하므로 처리 속도가 느린 경우가 많다.
DERIVED
단위 SELECT 쿼리의 샐행 결과로 메모리나 디스크에 임시 테이블을 생성하는 것을 의미한다. MySQL 5.6 버전부터는 옵티마이저 옵션에 따라 쿼리 특성에 맞게 임시 테이블에도 인덱스를 추가해서 만들 수 있게 최적화되었다.
가능하다면 DERIVED 형태의 실행 계획을 조인으로 해결할 수 있도록 비꿔주는 것이 좋다.
DEPENDENT DERIVED
LATERAL JOIN을 통해 FROM 절의 서브쿼리에서 외부 컬럼을 참조하는 쿼리(MySQL 8.0 버전부터 사용 가능)
UNCACHABLE SUBQUERY
서브쿼리에 캐시 사용이 불가능한 경우
- 사용자 변수가 서브쿼리에 사용된 경우
- NOT-DETERMINISTIC 속성의 스토어드 루틴이 서브쿼리 내에 사용된 경우
- UUID()나 RAND()와 같이 결괏값이 호출할 때마다 달라지는 함수가 서브쿼리에 사용된 경우
쿼리 문장에 하나의 서브쿼리가 적혀 있다고, 해당 서브쿼리가 한 번만 실행된다는 것을 의미하진 않는다. 하지만 조건이 똑같은 서브쿼리가 실행될 때는 다시 실행하지 않고 내부적인 캐시 공간을 사용한다.
SUBQUERY 캐시 사용
- SUBQUERY: Outer의 영향을 받지 않으므로 처음 한 번만 실행해서 결과를 캐시
- DEPENDENT SUBQUERY: Outer 컬럼의 값 단위로 캐시
UNCACHEABLE UNION
UNION 타입 쿼리에 캐시 사용이 불가능한 경우
MATERIALIXED
FROM 절이나 IN subquery 형태의 서브쿼리의 최적화를 위해 사용. MySQL 5.6 버전부터 도입
MySQL 5.7 버전부터 서브쿼리의 내용을 구체화(Materialization)한 후 조인하는 형태로 최적화
- 서브쿼리
- 중첩된 쿼리(Nested Query): SELECT 컬럼에 사용된 서브쿼리
- 서브쿼리(Subquery): WHERE 절에 사용된 서브쿼리
- 파생 테이블(Derived Table): FROM 절에 사용된 서브쿼리, 일반적인 RDBMS에서는 인라인 뷰, 서브 셀렉트라고 부름
- 반환하는 값의 특성에 따른 서브쿼리 구분
- 스칼라 서브쿼리(Scalar Subquery): 컬럼이 하나인 레코드 한 건만 반환하는 쿼리
- 로우 서브쿼리(Row Subquery): 컬럼의 개수와 관계없이 하나의 레코드만 반환하는 쿼리
table 컬럼
MySQL서버의 실행 계획은 단위 SELECT 쿼리 기준이 아닌 테이블 기준으로 표시된다.
- 별도의 테이블을 사용하지 않는 SELECT 쿼리는 NULL로 표시
- <>로 둘러싸인 테이블 이름은 임시 테이블을 의미한다. <>에 표시되는 숫자는 실행 계획의 id를 의미
partitions 컬럼
파티션된 테이블 중 어떤 테이블을 사용하는지 표시
type 컬럼
MySQL 서버가 각 테이블의 레코드를 어떤 방식으로 읽었는지 표시
ALL을 제외한 모든 접근 방법은 인덱스를 사용한다. index_merge를 제외한 모든 타입은 하나의 인덱스를 사용한다.
system
레코드가 1건 이하로 존재하는 테이블을 참조하는 형태의 접근 방법
InnoDB 에선 사용하지 않고, MyISAM 과 MEMORY 엔진에서만 사용한다.
const
프라이머리 키나 유니크 키 컬럼을 이용하는 WHERE절을 이용하여 반드시 1건을 반환하는 처리 방식. UNIQUE INDEX SCAN 이라고도 한다.
eq_ref
여러 테이블이 조인되는 쿼리에서 표시. 조인에 사용되는 드리븐 테이블의 컬럼 값이 프라이머리 키나 유니크 키 컬럼의 검색 조건에 사용할 때를 가리킨다. 즉, 조인에서 이후에 읽는 테이블에서 반드시 1건만 존재한다는 보장이 있어야 사용할 수 있는 접근 방법이다.
ref
조인의 순서와 없이 사용되며, 프라이머리 키나 유니크 키의 제약 조건이 없다. 반환되는 레코드가 1건이라는 보장이 없으므로 const나 eq_ref보다는 빠르지 않다. 하지만 동등한 조건으로만 비교되므로 매우 빠른 레코드 조회 장법의 하나이다.
fulltext
MySQL 서버의 전문 검색(Full-text Search) 인덱스를 사용해 레코드를 읽는 접근 방법
일반 인덱스의 접근 방법이 const, eq_ref, ref가 아니면 일반적으로 MySQL은 전문 인덱스를 사용하는 조건을 선택해서 처리한다. range 접근 방법이 더 빨리 처리되는 경우도 많으므로 조건별로 성능을 확인해 보는 편이 좋다.
MATCH (…) AGAINST (…) 구문을 사용하여 실행
ref_or_null
ref 접근 방법과 같지만, NULL 비교가 추가된 형태이다. WHERE a='?' OR a IS NULL 과 같은 쿼리 실행시 표시
unique_subquery
WHERE 조건절에서 사용될 수 있는 IN 형태의 쿼리 접근 방법. 서브쿼리에서 중복되지 않는 유니크한 값만 반환할 때 사용
세미조인 최적화를 이용할 경우 조인을 사용하여 처리하기 때문에 MySQL 8.0 버전부터는 IN절 쿼리여도 다른 실행 계획을 나타낼 수 있다.
index_subquery
중복된 값이 존재하지 않는 다는 보장을 가지는 unique_subquery와 달리, 중복된 값을 반환할 경우가 있을 경우 사용되는 접근 방법
range
인덱스 레인지 스캔 형태의 접근 방법. <, >, IS NULL, BETWEEN, IN, LIKE와 같은 연산을 통해 범위 검색을 하는 경우 사용된다.
- 인덱스 레인지 스캔
- const, ref, range를 묶어서 지칭
- 인덱스를 효율적으로 사용한다, 작업 범위 결정 조건으로 인덱스를 사용한다고 할 때 해당 접근 방법을 의미한다.
index_merge
2개 이상의 인덱스를 이용하여 각각의 검색 결과를 만들어낸 후, 그 결과를 병합하여 처리하는 형식
- 여러 인덱스를 읽어야 하므로, 일반적으로 range 접근 방법보다 효율성이 떨어진다.
- 전문 검색 인덱스를 사용하는 쿼리에서는 index_merge가 적용되지 않는다.
- index_merge 접근 방법으로 처리된 결과는 항상 2개 이상의 집합이 되기 때문에, 교집합/합집합/중복 제거 등 부가적인 작업이 필요하다.
index
인덱스 풀 스캔 방식
- range, const, ref 와 같은 접근 방법으로 인덱스를 사용하지 못하는 경우
- 인덱스에 포함된 컬럼만으로 처리할 수 있는 쿼리인 경우
- 인덱스를 이용하여 정렬이나 그루핑 작업이 가능한 경우
ALL
테이블 풀 스캔을 의미하는 접근 방법
가장 마지막에 선택하는 가장 비효율적인 방법이다.
MySQL 서버에서 인접한 페이지가 연속하여 읽히면, 최대 64개 페이지씩 한꺼번에 디스크로 읽어 들이기 때문에 페이지를 하나씩 읽어 들이는 작업보다 빠르게 레코드를 읽을 수 있다. 이를 리드 어헤드라고 한다. innodb_read_ahead_threshold 시스템 변수와 innodb_random_read_ahead 시스템 변수를 이용하여 리드 어헤드를 언제 실행할 지 제어할 수 있다.
possible_keys 컬럼
실행 계획에 사용을 고려한 인덱스 목록
key 컬럼
최종 선택된 실행 계획에서 사용하는 인덱스
key_len 컬럼
다중 컬럼으로 구성된 인덱스에서 몇 바이트까지 사용했는지 알려주기 때문에, 이를 통해 몇 개의 컬럼을 사용했는지 알 수 있다.
- utf8mb4에선 한 문자당 고정적으로 4바이트 이므로 key_len이 16일 경우 char(4)를 의미한다.
- integer은 4바이트, date는 3바이트
- NOT NULL이 아닌 컬럼은 NULL인지 아닌지 결정하기 위해 1바이트를 추가로 사용한다.
ref 컬럼
접근 방법이 ref일 경우 참조 조건(비교 조건)으로 어떤 값을 사용했는지 표시
참조 값을 변환할 시엔 func으로 표시된다.
rows 컬럼
스캔할 레코드 건수를 예측하여 표시
통계 정보를 참조하여 산출해 낸 예상값이기 때문에 정확하지 않다.
filtered 컬럼
rows가 인덱스 스캔 혹은 풀 스캔을 사용하여 가져올 레코드를 예측한 건수라면, filtered는 가져올 레코드 중 쿼리의 조건에 일치하는 비율을 예측하여 표시
즉, 예측한 실제 데이터 갯수는 rows*filtered/100 이 된다.
Extra 컬럼
const row not found
const 접근 방법으로 테이블을 읽었지만 레코드가 존재하지 않는 경우
Deleting all rows
MyISAM과 같이 스토리지 엔진의 핸들러 차원에서 테이블의 모든 레코드를 삭제하는 기능을 제공하는 엔진 테이블인 경우, 핸들러 기능을 한번만 호출한 것을 의미한다.
WHERE 조건절이 없는 DELETE 문장에서 주로 표시
MySQL 8.0 버전에서는 표시되지 않는다.
Distinct
레코드를 중복 없이 유니크하게 가져오기 위한 쿼리
FirstMatch
세미 조인의 최적화 중 FirstMatch 전략이 사용되었을 경우 표시
첫 번째로 일치하는 한 건만 검색한다는 것을 의미
Full scan on NULL key
col1 IN (SELECT col2 FROM …) 과 같은 쿼리에서 col1의 값이 nullable 하다면 해당 쿼리는 NULL IN (SELECT col2 FROM …)과 같이 바뀐다. 이때 MySQL은 NULL값에 대한 연산 규칙을 가지고 있는데,
- MySQL의 NULL 연산 규칙
- 서브쿼리가 결과 레코드를 가진다면 결과는 NULL
- 서브쿼리가 결과 레코드를 가지지 않는다면 결과는 FALSE
이 과정에서 col1이 NULL이면 subquery에서 index lookup-based 최적화 전략을 사용할 수 없기 때문에 MySQL은 풀 스캔 전략을 사용하는 fallback strategy를 사용하게 된다. 참고
Impossible HAVING
HAVING절 조건을 만족하는 레코드가 없을 때 표시
Impossible WHERE
WHERE절 조건을 만족하는 레코드가 없거나 항상 FALSE일 경우 표시
LooseScan
세미 조인 최적화 중 LooseScan 최적화 전략이 사용되면 표시
No matching min/max row
min/max와 같은 집합 함수가 있는 쿼리에 일치하는 레코드가 없는 경우 표시
No matching row in const table
const 방법으로 접근할 때 일치하는 레코드가 없는 경우 표시
No matching rows after partition pruning
UPDATE하거나 DELETE할 대상 파티션이 없을 경우 표시
- 파티션 프루닝: 쿼리 실행에 필요한 파티션만 선택하는 과정
No tables used
FROM 절 자체가 없거나 상수 테이블을 의미하는 DUAL이 사용될 경우 표시
Not exists
LEFT JOIN을 이용하여 not exists를 처리할 경우 표시
Plain isn’t ready yet
EXPLAIN FOR CONNECTION을 이용하여 실행 계획을 볼 경우 확인할 수 있으며, 해당 커넥션에서 쿼리의 실행 계획을 수립하지 못한 상태를 표시
Range checked for each record(index map: N)
조인 조건이 둘 다 변수인 경우, 인덱스 레인지 스캔과 풀 스캔 중 최적의 경우가 레코드마다 다를 수 있는다. 이런 경우 레코드마다 인덱스 레인지 스캔을 할 것인지 체크한다를 의미한다. 16진수 숫자는 possible_keys를 의미한다.
Recursive
MySQL 8.0 버전부터는 CTE(Common Table Expression)을 이용하여 재귀 쿼리를 작성할 수 있게 되었다. WITH 구문을 통해 재귀 CTE를 사용할 경우 표시
Rematerialize
LATERAL JOIN에서 선행 테이블의 레코드별로 서브쿼리를 실행하는 것을 의미
Select tables optimized away
MIN, MAX가 SELECT 절에 사용되거나 GROUP BY 절과 함께 인덱스를 사용할 수 있다면, 오름차순 또는 내림차순으로 1건만 읽는 형태의 최적화가 적용된다.
MyISAM 테이블의 경우 전체 레코드를 별도로 관리하기 때문에, GROUP BY 없이 COUNT(*)또한 이런 형태의 최적화가 적용된다.
Start temporary, End temporary
세미 조인 최적화 중 Duplicate Weed-out 최적화 전략이 사용되었다는 표시
unique row not found
유니크 컬럼으로 아우터 조인을 수행할 때, 아우터 테이블에 일치하는 레코드가 존재하지 않음
Using filesort
ORDER BY를 수행할 적절한 인덱스를 찾지 못하여 조회된 레코드를 다시 한번 정렬해야 함을 의미한다.
조회된 레코드를 정렬용 메모리 버퍼(소트 버퍼)에 복사하여 퀵 소트 혹은 힙 소트 알고리즘을 이용하여 정렬을 수행한다. 이는 많은 부하를 일으키므로 가능하다면 쿼리를 튜닝하거나 인덱스를 생성하는 것이 좋다.
Using index(커버링 인덱스)
데이터 파일을 전혀 읽지 않고 인덱스만 읽어서 쿼리를 모두 처리할 수 있을 때 표시
Using index condition
인덱스 컨디션 푸시 다운 최적화 사용시 표시
Using index for group-by
GROUP BY 처리가 인덱스 컬럼을 이용하여 처리될 경우 표시
- 인덱스 스캔 종류에 따른 처리
- 타이트 인덱스 스캔을 통한 GROUP BY 처리
- 모든 인덱스를 다 읽어 GROUP BY를 처리해야 할 경우( e.g AVG, SUM, COUNT ) Using index for group-by 메세지가 출력되지 않는다.
- 루스 인덱스 스캔을 통한 GROUP BY 처리
- 그루핑 컬럼에 대해서만 조회하거나 단일 값만 읽어도 되는 경우 ( e.g. MIN, MAX )는 루스 인덱스 스캔이 사용된다.
- 타이트 인덱스 스캔을 통한 GROUP BY 처리
- WHERE 절과 GROUP BY 절
- WHERE 조건절이 없는 경우: GROUP BY 절에서 인덱스를 사용할 수 있는 경우 루스 인덱스 스캔
- WHERE 조건절이 인덱스를 사용하지 못하는 경우: WHERE 절을 위해 데이터 레코드를 읽어야 하므로 타이트 인덱스 스캔을 통해 GROUP BY 처리
- WHERE 조건절이 인덱스를 사용하는 경우: WHERE 절의 인덱스와 같은 인덱스를 사용하는 경우만 GROUP BY 에서 루스 인덱스 스캔, 인덱스가 다른 경우 WHERE 조건의 인덱스만 사용
Using index for skip scan
인덱스 스킵 스캔 최적화 사용시 표시
Using join buffer(Block Nested Loop, Batched Key Access, Hash Join)
일반적으로 빠른 쿼리 실행을 위해 조인되는 컬럼은 인덱스를 생성한다. 하지만 드리븐 테이블에 검색을 위한 적절한 인덱스가 없다면 MySQL 서버는 Block Nested Loop Join 이나 Hash Join을 통해 조인 버퍼를 사용한다. 이렇게 조인 버퍼가 사용되는 경우 표시한다.
Using MRR
레코드를 한 건 한 건씩 읽는 문제점을 보완하기 위해 여러 개의 키 값을 한 번에 MRR 엔진으로 전달하여 최소한의 디스크 접근하는 방식
Using sort_union(…), Using union(…), Using intersect(…)
index_merge 접근 방법으로 실행하여 인덱스 병합 방식 표시
- Using intersect(…): 인덱스를 사용할 수 있는 조건이 AND로 연결된 경우 교집합 추출
- Using union(…): 인덱스를 사용할 수 있는 조건이 OR로 연결된 경우 합집합 추출, 대체로 동등 비교에서 사용
- Using sort_union(…): Using union과 같은 작업이지만 대량의 range 조건들로 인해 Using union으로 처리될 수 없는 경우. 프라이머리 키만 먼저 읽어서 정렬하고 병합한 이후 레코드를 읽어서 반환
Using temporary
쿼리를 처리하는 동안 중간 결과를 담아 두기 위해 임시 테이블을 사용할 경우 표시
임시 테이블은 메모리상일 수도 디스크상일 수도 있다. 실행 계획이 아닌 SHOW STATUS LIKE 'Created_tmp%' 명령어를 이용하여 확인할 수 있다.
- Using temporary는 표시되지 않지만 임시 테이블이 생성되는 경우
- FROM 절의 서브쿼리
- COUNT(DISTINCT col1)
- UNION, UNION DISTINCT
Using where
MySQL 엔진 레이어에서 별도의 가공 혹은 필터링 작업을 처리한 경우 표시
filtered 컬럼과 비교하여 성능상의 이슈를 판별하여야 한다. 스토리지 엔진에서 읽은 엔진에 비해 필터링된 레코드가 적을 경우 비효율적인 쿼리
Zero limit
테이블의 메타 정보만 필요하여 LIMIT 0을 사용하는 경우 표시
'Database > MySQL' 카테고리의 다른 글
[MySQL] 파티션 (0) | 2022.12.06 |
---|---|
[MySQL] 트랜잭션과 잠금 (0) | 2022.10.09 |
[MySQL] MySQL 엔진 아키텍처 (1) | 2022.10.04 |
[MySQL] InnoDB의 B-Tree Index (0) | 2022.09.04 |