백은빈, 이성욱 님의 "Real MySQL" 책을 정리한 포스팅 입니다.
1. id 칼럼
- 테이블들이 조인될 경우, 같은 id 값을 할당받음
- id 값은 테이블의 접근 순서를 의미하지 않음
- 같은 id라면 먼저 올라온 것이 먼저 접근된 순서임을 의미함
2. select_type 칼럼
- 각 단위 select 쿼리가 어떤 타입의 쿼리인지 표시되는 컬럼
select_type | 설명 | 예제 |
SIMPLE | 단순한 SELECT 쿼리로, 서브쿼리를 사용하지 않음. | |
PRIMARY | UNION이나 서브쿼리를 포함하는 쿼리에서 가장 바깥쪽의 SELECT 쿼리. | |
UNION | UNION으로 결합된 SELECT 쿼리 중 두 번째 이후의 쿼리. | |
DEPENDENT UNION | 서브쿼리 내에서 UNION 또는 UNION ALL로 결합된 테이블. | |
UNION RESULT | UNION 결과를 담아내는 테이블. UNION ALL에서는 사용되지 않음. |
|
SUBQUERY | 서브쿼리 내에서 사용되는 테이블. | |
DEPENDENT SUBQUERY | SELECT에서 사용된 서브쿼리에 참조되는 테이블. | |
DERIVED | FROM 절에 서브쿼리가 사용된 경우, 그 결과를 임시 테이블로 저장. | SELECT * FROM (SELECT de.emp_no FROM dept_emp de GROUP BY de.emp_no) tb; |
DEPENDENT DERIVED | 서브쿼리에서 외부 칼럼을 참조하는 경우 | SELECT * FROM employees e LEFT JOIN LATERAL ( SELECT * FROM salaries s WHERE s.emp_no = e.emp_no ) AS s2 ON s2.emp_no = e.emp_no |
UNCACHEABLE SUBQUERY | 서브쿼리의 결과가 캐시되지 않는 경우 결과가 매번 달라지는 서브쿼리에서 사용됨. |
사용자 변수 NOT-DETERMINISTIC 속성의 (스토어드 루틴) UUID()나 RAND() |
UNCACHEABLE UNION | UNION의 결과가 캐시되지 않는 경우. | |
MATERIALIZED | 서브쿼리를 임시 테이블로 구체화한 후, 해당 임시 테이블을 사용하여 최적화하는 기법. |
3. Table 컬럼
- 실행 계획은 테이블 기준으로 표시됨 (쿼리 기준 X)
- <>로 둘러쌓인 이름: 임시 테이블
- <>로 둘러쌓인 숫자: select 쿼리의 id 값
4. partitions 컬럼
- 파티션은 물리적으로 개별 테이블처럼 저장 공간을 가짐
파티션 프루닝
- 파티션이 여러 개인 테이블에서 불필요한 파티션을 빼고 쿼리를 수행하기 위해 접근해야 할 것으로 판단되는 테이블만 골라내는 과정
5. type 컬럼
- MySQL 서버가 각 테이블의 레코드를 어떤 방식으로 읽었는지를 나타냄
type | 설명 | |
system | 레코드가 한 건만 존재하거나, 한 건도 존재하지 않는 테이블을 참조하는 방식. (MyISAM 또는 MEMORY 엔진에서만 사용) |
|
const | 프라이머리 키 또는 유니크 키의 모든 컬럼을 포함한 동등 조건이 있을 때 사용. 반드시 1건을 반환하는 쿼리. 인덱스의 일부 컬럼만 조건으로 사용할 때는 사용 불가. |
SELECT * FROM users WHERE user_id = 123; |
eq_ref | 첫 번째 테이블의 칼럼값을 이용해 두 번째 테이블의 프라이머리 키 또는 유니크 키로 동등 조건 검색 반드시 1건을 반환하는 쿼리. 조인에서 사용됨 |
SELECT o.order_id, c.customer_name FROM orders o JOIN customers c ON o.customer_id = c.customer_id WHERE o.order_id = 101; |
ref | 동등 조건으로 검색할 때 사용 반드시 1건만 반환될 필요는 없음. 조인에서 사용됨 |
SELECT o.order_id, c.customer_name FROM orders o JOIN customers c ON o.customer_id = c.customer_id WHERE c.city = 'Seoul'; |
fulltext | 전문 검색 인덱스를 사용하여 레코드를 읽는 접근 방법. | |
ref_or_null | ref와 같으나, null 비교가 추가된 형태. | SELECT o.order_id, c.customer_name FROM orders o LEFT JOIN customers c ON o.customer_id = c.customer_id WHERE c.city = 'Seoul' OR c.city IS NULL; |
unique_subquery | IN 연산자의 쿼리에서 서브쿼리 결과로 중복되지 않는 유니크한 값만 반환할 때 사용. | |
index_subquery | IN 연산자에서 중복된 값을 제거할 수 있을 때 사용. 서브쿼리 결과에서 중복 값을 인덱스를 이용해 제거. | |
range | 인덱스 레인지 스캔 방식의 접근 방법. | |
index_merge | 2개 이상의 인덱스를 이용해 각각의 검색 결과를 만들어낸 후, 그 결과를 병합하여 처리. 집합 연산 같은 부가적인 작업이 필요. |
SELECT * FROM users WHERE (first_name = 'John' AND last_name = 'Doe') OR (email = 'john.doe@example.com'); |
index | 인덱스 풀 스캔을 의미. | |
ALL | 풀 테이블 스캔을 의미. |
6. possible_keys 컬럼
- 실행 계획을 만들기 위해 후보로 선정했던 접근 방법에서 사용된 인덱스 목록
7. key 컬럼
- 최종 선택된 실행 계획에서 사용하는 인덱스
8. key_len 컬럼
- 처리를 위한 인덱스 사용 중, 각 레코드에서 몇 바이트까지 컬럼을 사용했는지 알려줌
9. ref 컬럼
- 접근 방법이 ref 일때 사용됨
- 참조 조건으로 어떤 값이 제공됐는지 보여줌
const
SELECT * FROM orders WHERE order_date = '2025-01-01';
- 상숫값
테이블 명과 칼럼명
SELECT o.order_id, c.customer_name
FROM orders o JOIN customers c ON o.customer_id = c.customer_id
WHERE c.city = 'Seoul';
- 다른 테이블의 컬럼값
func
SELECT * FROM orders WHERE YEAR(order_date) = 2025;
- 컬럼값 가공
9. rows 컬럼
- 쿼리를 처리하기 위해 얼마나 많은 레코드를 읽어야 하는지를 의미함
- 인덱스를 사용하는 조건에만 일치하는 레코드 건수를 예측한 것
- 스토리지 엔진별로 가지고 있는 통계 정보를 참조해서 만듬 (산출된 예상값이라 정확하지 않음)
10. filtered 컬럼
- 인덱스 스캔이나 테이블 스캔에서 필터링된 레코드의 비율
SELECT * FROM users WHERE age >= 30;
- users 테이블에 1000개의 레코드가 있음
- age >= 30 을 만족하는 레코드가 200개라면, Filtered 컬럼 값은 200/1000 (0.2)가 됩니다.
11. Extra 컬럼
일반적인 실행 정보
메시지 | 설명 |
const row not found | 조건을 만족하는 레코드가 없을 때 발생 (비용 타입의 테이블에서 조건을 만족하는 레코드 없음) |
Deleting all rows | 테이블의 모든 레코드를 삭제하는 경우 |
Distinct | SELECT DISTINCT 구문으로 중복이 제거되는 경우 |
FirstMatch | 조인을 최적화하여 첫 번째 일치하는 행을 찾으면 바로 종료하는 경우 (SEMI-JOIN 최적화) |
조건 관련 메시지
메시지 | 설명 |
Full scan on NULL key |
NULL 값을 포함하는 FULLTEXT 검색에서 발생
|
Impossible HAVING |
HAVING 절에서 조건을 만족하는 레코드가 없을 때 발생
|
Impossible WHERE |
WHERE 절에서 조건을 만족하는 레코드가 없을 때 발생
|
LooseScan |
GROUP BY 최적화 기법인 LooseScan이 실행되는 경우
|
테이블 관련 메시지
메시지 | 설명 |
No matching min/max row |
MIN() 또는 MAX()가 사용되었으나 일치하는 행이 없을 때 발생
|
No matching row in const table |
const 타입으로 처리되는 테이블에 일치하는 행이 없을 때 발생
|
No matching row after partition pruning |
파티션 테이블에서, 조건에 맞는 파티션이 없을 때 발생
|
No tables used |
FROM 절 없이 실행되는 쿼리
|
Not exists |
LEFT JOIN 최적화, 조인된 테이블에 일치하는 레코드가 없으면 즉시 종료
|
실행 계획 및 최적화 관련 메시지
메시지 | 설명 |
Plan isn't ready yet |
실행 계획이 아직 생성되지 않았을 때 발생
|
Range checked for each record (index map: N) |
동적 인덱스 레인지 스캔 최적화가 적용될 때 발생,
모든 행을 하나씩 검사하여 인덱스 범위를 결정 |
Recursive |
WITH RECURSIVE 구문으로 재귀적으로 실행되는 경우
|
Rematerialize |
Derived 테이블이 여러 번 실행될 때 필요할 때마다 다시 생성됨
|
Select tables optimized away |
집계함수(GROUP BY, DISTINCT, COUNT()) 최적화가 적용된 경우, 테이블을 직접 조회하지 않음
|
Start temporary, End temporary |
쿼리 실행 중에 임시 테이블이 사용된 경우
|
정렬 및 임시 테이블 관련 메시지
메시지 | 설명 |
Using filesort |
ORDER BY를 위한 추가적인 정렬 연산이 필요한 경우
|
Using index |
커버링 인덱스를 사용하여 테이블을 직접 조회하지 않고 인덱스에서 필요한 정보를 가져옴
|
Using index condition |
Index Condition Pushdown 최적화가 적용된 경우
|
Using index for group-by |
GROUP BY가 인덱스를 사용하여 최적화된 경우
|
Using index for skip scan |
인덱스를 사용하여 스킵 스캔을 수행하는 경우
|
Using join buffer |
Block Nested Loop, Batched Key Access, Hash Join 등 조인 최적화 기법이 사용된 경우
|
Using MRR |
Multi-Range Read 최적화가 사용된 경우
|
Using sort_union |
여러 개의 인덱스를 사용하여 결과를 병합하는 경우
|
Using temporary |
임시 테이블을 사용하여 정렬, 집계 또는 중간 데이터 저장 수행
|
Using where |
WHERE 조건이 사용된 경우
|
Zero limit |
LIMIT 0이 사용된 경우
|
'Database > Mysql' 카테고리의 다른 글
[Real MySQL] 13-2. 파티션: 종류 (0) | 2025.03.13 |
---|---|
[Real MySQL] 13-1. 파티션: 개요 (0) | 2025.03.12 |
[Real MySQL] 10-2. 실행 계획: 실행 계획 확인 (0) | 2025.03.12 |
[Real MySQL] 10-1. 실행 계획: 통계 정보 (0) | 2025.03.12 |
[Real MySQL] 9-3. 옵티마이저와 힌트: 힌트 (0) | 2025.03.11 |