Extra 칼럼에는 주로 내부적인 처리 알고리즘에 대해 더 깊이 있는 내용을 보여주는 경우가 많다.
dept_no만 중복 없이 유니크하게 가져오기 위한 쿼리다.
dept_no가 발생하게 되기 때문에 DISTNCT 명령을 추가했다.EXPLAIN
SELECT DISTINCT d.dep_no
FROM departments d, dept_emp de
WHERE de.dept_no=d.dept_no;
| id | select_type | table | type | key | Extra |
|---|---|---|---|---|---|
| 1 | SIMPLE | d | index | ux_deptname | Using index; Using temporary |
| 1 | SIMPLE | de | ref | PRIMARY | Using index; Distinct |

DISTINCT를 처리하기 위해 조인하지 않아도 되는 항목은 모두 무시하고 필요한 것만 조인한 것을 나타낼 때 Extra 칼럼에 Distinct가 표시된다.col1 IN (SELECT col2 FROM …)과 같은 쿼리에서 col1이 NULL인 경우 서브 쿼리에 대해 풀 테이블 스캔을 해서 다음 2가지 중 결론을 내야 한다.
NULLFALSEcol1이 NULL인 경우 서브 쿼리 테이블을 풀 테이블 스캔 하는 경우다.
col1이 NOT NULL로 정의된 칼럼이라면 표시되지 않을 것이다.IN이나 NOT IN 연산자의 왼쪽 값이 NULL인 레코드가 있고 서브 쿼리에 개별적인 WHERE 조건이 지정돼 있다면 풀 테이블 스캔으로 인해 성능 문제가 발생할 수 있다.
NOT NULL이 아니라면 WHERE 조건절에 IS NOT NULL 조건을 추가하는 것도 방법이다.HAVING 절 조건을 만족하는 레코드가 없을 때 표시된다.WHERE 조건이 항상 FALSE가 될 수밖에 없는 경우 표시된다.WHERE 조건절에 만족하는 레코드가 하나도 없지만 MIN()이나 MAX() 같은 집합 함수가 있는 쿼리엔 ‘No matching min/max row’가 표시된다.EXPLAIN SELECT MIN(dept_no), MAX(dept_no)
FROM dept_emp WHERE dept_no='';
UPDATE나 DELETE 쿼리를 실행할 때 표시된다.FROM 절이 없거나 FROM DUAL 형태의 쿼리 실행 계획에서 표시된다.
SELECT 1;, SELECT 1 FRO dual;FROM 절이 없는 쿼리도 허용된다.NOT IN(subquery)나 NOT EXISTS 연산자를 사용할 때 실행되는 조인을 뜻한다.dept_emp 테이블 레코드로 departments 테이블을 조인할 때 departments 테이블의 레코드가 존재하는지 아닌지만 판단한다.
EXPLAIN SELECT *
FROM dept_emp de
LEFT JOIN departments d ON de.dept_no=d.dept_no
WHERE d.dept_no IS NULL;
| id | select_type | table | type | key | Extra |
|---|---|---|---|---|---|
| 1 | SIMPLE | de | ALL | NULL | NULL |
| 1 | SIMPLE | d | eq_ref | PRIMARY | Using where; Not exists |
아래 쿼리는 사원의 사원 번호가 다른 사원의 사원 번호보다 큰 경우에 모두 조회하는 쿼리이다.
EXPLAIN SELECT *
FROM employees e1, employees e2
WHERE e2.emp_no >= e1.emp_no;
emp_no가 작을 때는 풀 테이블 스캔으로 접근하고 크면 인덱스 레인지 스캔으로 접근하게 된다.e1.emp_no=1이라면 e2 테이블의 거의 모든 레코드를 읽어 e2.emp_no≥e1.emp_no 조건을 만족하는 레코드를 찾아야 한다.WITH 구문을 이용해 CTE를 사용할 수 있다.
WITH RECURSIVE cte (n) AS
(
SELECT 1
UNION ALL
SELECT n + 1 FROM cte WHERE n < 5
)
SELECT * FROM cte;
SELECT 쿼리에서는 임시 테이블을 풀 스캔해서 결과를 반환한다.| id | select_type | table | type | key | Extra |
|---|---|---|---|---|---|
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL |
| 2 | DERIVED | NULL | NULL | NULL | No tables used |
| 3 | UNION | cte | ALL | NULL | Recursive; Using where |
WITH 구문을 이용한 CTE가 사용됐다고 해도 재귀 쿼리가 아니면 Recursive가 표시되지 않는다.아래 예제를 살펴 보자.
EXPLAIN SELECT * FROM employees e
LEFT JOIN LATERAL (
SELECT *
FROM salaries s
WHERE s.emp_no=e.emp_no
ORDER BY s.from_date DESC LIMIT 2
) s2 ON s2.emp_no=e.emp_no
WHERE e.first_name='Matt';
| id | select_type | table | type | key | Extra |
|---|---|---|---|---|---|
| 1 | PRIMARY | e | ref | ix_firstname | Rematerialize(<derived2>) |
| 1 | PRIMARY | <derived2> | ref | NULL | |
| 2 | DEPENDENT DERIVED | s | ref | PRIMARY | Using filesort |
employees 테이블 레코드마다 salaries 테이블에서 emp_no가 일치하는 레코드 중 from_date 칼럼 역순으로 2건만 가져와 임시 테이블 derived2로 저장employees 테이블과 derived2 테이블을 조인employees 레코드마다 새로 내부 임시 테이블을 생성MIN() 또는 MAX()만 SELECT 절에 사용됨GROUP BY로 MIN(), MAX()를 조회하는 쿼리가 인덱스를 오름차순 또는 내림차순으로 1건만 읽기EXPLAIN SELECT MAX(emp_no), MIN(emp_no) FROM employees;
EXPLAIN SELECT MAX(from_date), MIN(fron_date) FROM salaries WHERE emp_no=10002;
emp_no가 인덱스이기 때문에 employees 테이블 인덱스의 레코드의 제일 처음과 제일 마지막만 읽어 최적화가 가능하다.(emp_no, from_date)로 인덱스가 있기 때문에 emp_no=10002를 먼저 검색하고 오름차순 또는 내림차순으로 하나만 조회하면 되기에 이런 최적화가 가능하다.ORDER BY를 처리할 때 적절한 인덱스를 사용하지 못할 때 표시된다.
GROUP BY 처리에 인덱스를 이용할 때 ‘Using index for group-by’가 표시된다.
GROUP BY 처리를 할 때 인덱스를 이용해 별도의 정렬 작업 없이 칼럼을 순서대로 읽으며 그루핑 작업을 수행한다.GROUP BY 처리를 위해 인덱스를 읽는 방법을 루스 인덱스 스캔이라고 한다.
GROUP BY를 인덱스로 처리할 수 있더라도 집계 연산이 포함된 쿼리는 루스 인덱스 스캔 처리를 할 수 없다.
MIN()이나 MAX() 등으로 조회할 때 루스 인덱스 스캔을 사용할 수 있다.WHERE 절에서 사용하려는 인덱스에 의해서도 GROUP BY 인덱스 사용 여부가 영향을 받는다.
WHERE 조건절이 없는 경우엔 GROUP BY 절과 SELECT로 가져오는 칼럼이 루스 인덱스 스캔을 사용할 수 있는 조건만 갖추면 된다.WHERE 조건절이 있지만 조건절이 인덱스를 사용하지 못하면 타이트 인덱스 스캔(인덱스 스캔) 과정을 통해 처리 된다.WHERE 절의 조건과 GROUP BY 처리가 똑같은 인덱스를 공통으로 사용할 수 있을 때만 루스 인덱스 스캔을 사용할 수 있다.
WHERE 절과 GROUP BY 절이 다른 인덱스를 각각 사용하는 경우에 일반적으로 WHERE 절의 인덱스를 사용하는 경향이 있다.GROUP BY와 WHERE 절을 함께 사용하는 쿼리에서 루스 인덱스 스캔을 사용할 수 있는 상황이더라도 검색된 레코드 수가 적다면 루스 인덱스 스캔을 사용하지 않을 수도 있다.
join_buffer_size 시스템 변수로 조인 버퍼 크기를 설정할 수 있다.
GROUP BY 쿼리가 대표적인 임시 테이블을 사용하는 쿼리이다.
GROUP BY 칼럼과 ORDER BY 칼럼이 다른 쿼리의 경우도 임시 테이블이 필요한 작업이다.FROM 절에 서브 쿼리를 사용하는 경우 (파생 테이블이라 부름)COUNT(DISTINCT column1)를 포함하는 쿼리가 인덱스를 사용할 수 없는 경우UNION이나 UNION DISTINCT가 사용된 쿼리LIMIT 0을 사용하면 데이터 값이 아닌 결과값의 메타데이터만 가져올 수 있는데 이 때 ‘Zero limit’가 표시된다.