EXPLAIN
은 MySQL이 어떻게 쿼리를 실행하는 지에 관한 정보를 보여주는 명령어입니다.
MySQL 5.6.3 이전 버전에서는 SELECT만 가능(Update, Delete의 경우 SELECT로 변환하여 수행해야 함)했으며,
MySQL 5.6.3 이후 버전은 SELECT, DELETE, INSERT, UPDATE, REPLACE 으로 기능이 확대되었고,
MySQL 8.0.18 이후부터는 EXPLAIN ANALYZE
를 사용할 수 있게 되었습니다.
explain 내역에서 1 warning
이라고 뜨는 것을 확인하기 위해 show warnings
명령어를 사용할 수 있습니다.
해당 내용을 통해 옵티마이저가 쿼리를 어떻게 변경했는지 볼 수 있습니다.
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | a | NULL | ALL | NULL | NULL | NULL | NULL | 14 | 100.00 | Using where |
2 | DEPENDENT SUBQUERY | b | NULL | ref | PRIMARY | PRIMARY | 8 | edu.a.ordr_no | 1 | 100.00 | Using index |
참고) filter가 100에 가까울수록 잘 활용된 것 (실제 사용 row수 / 파일에서 읽어온 row 수) * 100
SIMPLE
PRIMARY
UNION
DEPENDENT UNION
UNION RESULT
SUBQUERY
DEPENDENT SUBQUERY
DERIVED
MATERIALIZED
UNCACHEABLE SUBQUERY
UNCACHEABLE UNION
참고) Cacheable VS Uncheable
테이블을 어떻게 접근할 것인지에 대한 내용입니다. (중요❗️)
select_type 만으로는 튜닝거리인지 판단할 수 없어 type을 함께 확인하여야 합니다.
* MySQL 매뉴얼 상에는 join type이라고 되어 있으나 access type으로 해석하는 것이 일반적임.
옵티마이저가 어떤 기법으로 최적화를 시켰는지에 대한 정보입니다.
구분 | 설명 |
---|---|
const row not found | 대상테이블이 empty인 경우 |
Distinct | 첫번째 매칭되는 row를 찾는 경우 이후의 row는 탐색을 중단 Distinct 처리에 사용 |
FirstMatch | semi join first match 전략을 사용한 경우 |
Full scan on NULL key | subquery 사용시 outer에서 null값을 공급받는 경우 |
Impossible HAVING | Having 절이 항상 false인 경우 |
Impossible WHERE | where절이 항상 false인 경우 ( ex. not null 컬럼에 대한 is null 비교 ) |
Impossible WHERE noticed after reading const tables | 실행계획을 생성하는 과정에서 system/const type의 경우 이를 미리 실행해보고 false임을 판단 |
Materialize( 5.6.7 이후 ) | subquery를 maerialize 형태로 최적화하는 경우 |
No tables used | From절이 없거나 From절에 dual과 같은 상수 테이블이 사용되는 경우 ( ex. select 1; ) |
Not Exists | left join형태의 anti join을 not exists 형태로 최적화하는 경우 |
Range checked for each record( index map : N ) | 선행테이블의 레코드마다 range나 index merge등 인덱스를 사용할 수 있는지 체크 |
Select tables optimized away | 인덱스 lookup 후 1개의 결과만 리턴하는 경우( 쿼리실행X ). group by가 없는 aggregate 함수만 포함되거나 커버링 인덱스의 선행컬럼이 =조건인 쿼리의 후행컬럼을 aggregation하는 경우 |
Using filesort 👎 | 물리적인 정렬작업 수행 |
Using index 👍 | 데이터 파일을 읽지 않고 인덱스만 읽어서 결과를 처리할 수 있는 경우( 커버링 인덱스 ) |
Using index condition 👍 | where 조건이 storage engine에서 필터링되는 경우 ( 메모리에 데이터를 올릴 때 필터링을 거침 ) |
Using index for group by | using 인덱스와 유사. group by를 인덱스만 이용하여 처리 |
Using join buffer | full table scan을 피하기 위하여 드라이빙 테이블의 결과셋을 temp로 생성 |
Using MRR | non-clustered index를 통해 range 스캔하는 경우 중간에 random 버퍼를 이용하여 pk정렬수행 후 pk 순차스캔하는 효과를 발생시키는 경우 |
Using intersect/union/sort_union | index merge 최적화 |
Using temporary 🤔 | 쿼리처리를 위해 임시 테이블을 생성 |
Using where | mysql engine에서 데이터를 filter처리하는 경우 ( 메모리에서 필터링 ) |
SELECT * FROM TB1 USE INDEX( idx1) where ...
SELECT STRAIGHT_JOIN * FROM TB1, TB2 ...
```sql
mysql> select SQL_CALC_FOUND_ROWS * from cust; 61 rows in set (0.00 sec)
mysql> select FOUND_ROWS();
| 61 |
mysql> select SQL_CALC_FOUND_ROWS * from cust limit 1; 1 row in set (0.00 sec)
mysql> select FOUND_ROWS();
| 61 |
```
SELECT SQL_NO_CACHE * FROM TB1 ...
옵티마이저 힌트는 ANSI 표준으로 사용 가능합니다.
Hint Name | 설명 | Applicable Scopes |
---|---|---|
BKA, NO_BKA | BKA(배치키엑세스) join 사용 | Query block, table |
BNL, NO_BNL | BNL(블록네스티드루프) 사용 8.0.20 이후 해시조인 사용여부 제어 | Query block, table |
DERIVED_CONDITION_PUSHDOWN, NO_DERIVED_CONDITION_PUSHDOWN | 외부 쿼리 조건의 derived 테이블 사용여부 | Query block, table |
GROUP_INDEX, NO_GROUP_INDEX | group by 절 처리를 위한 인덱스 사용여부. Group by 처리에만 영향을 미침 | Index |
HASH_JOIN, NO_HASH_JOIN | 8.0.18에서만 사용 | Query block, table |
INDEX, NO_INDEX | group by, order by, where 절의 인덱스 사용여부. USE INDEX, IGNORE INDEX 에 대응 | Index |
INDEX_MERGE, NO_INDEX_MERGE | INDEX MERGE 사용여부 | Table, index |
JOIN_FIXED_ORDER | FROM절에 기술된 순서대로 조인, STRAIGHT_JOIN에 대응 | Query block |
JOIN_INDEX, NO_JOIN_INDEX | table access(row를 찾는 방법)에 대한 인덱스 사용여부 | Index |
JOIN_ORDER | 힌트에 명시된대로 조인 | Query block |
JOIN_PREFIX | 힌트에 명시된 테이블을 드라이빙으로 조인 | Query block |
JOIN_SUFFIX | 힌트에 명시된 테이블을 드리븐으로 조인 | Query block |
MAX_EXECUTION_TIME | 쿼리의 실행시간을 제어 | Global |
MERGE, NO_MERGE | 외부 쿼리 블록으로 derived table 또는 뷰를 병합 | Table |
MRR, NO_MRR | MRR 사용 | Table, index |
NO_ICP | 인덱스 컨디션 푸쉬다운 사용 | Table, index |
NO_RANGE_OPTIMIZATION | 인덱스 레인지를 비활성화. 인덱스를 사용하지 못하고 full scan으로 처리 | Table, index |
ORDER_INDEX, NO_ORDER_INDEX | order by 절 처리를 위한 인덱스 사용여부 | Index |
QB_NAME | 쿼리블록에 이름을 할당(쿼리블록에 대한 주석이라고 이해하면 됨) | Query block |
RESOURCE_GROUP | 쿼리실행의 리소스 그룹설정 | Global |
SEMIJOIN, NO_SEMIJOIN | 세미조인 사용여부 및 전략 선택 | Query block |
SKIP_SCAN, NO_SKIP_SCAN | 스킵스캔 사용여부 | Table, index |
SET_VAR | 쿼리실행을 위한 시스템 변수 제어. Ex) select /*+ SET_VAR(sort_buffer_size='8388608') */ from ... | Global |
SUBQUERY | 서브쿼리의 세미조인 최적화 전략 제어 | Query block |
만약 query_timeout이 10초로 설정되어있는데, 특정 커리만 시간 제한을 다르게 하고싶다면 MAX_EXECUTION_TIME
으로 힌트 설정 가능합니다.
또한 쿼리마다 시스템 변수를 제어하고 싶다면 SET_VAR
를 사용합니다.
* 참고 : https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html