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가지 중 결론을 내야 한다.
NULL
FALSE
col1
이 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’가 표시된다.