MySQL은 특정 쿼리를 수행할 때, 어떻게 하면 효율적으로 쿼리를 수행할지 계획을 세우는데, 이를 Optimizing(최적화)라고 부른다.
이러한 작업은 MySQL서버의 MySQL엔진에서 이루어지고, 최적화 작업을 하는데에 여러가지 기준이 있을 수 있지만 '비용 기반 최적화'방식으로 최적화를 하는 것이 일반적이다.
이번 글에서는 최적화 작업의 산물인 '실행 계획'에 대해서 살펴보도록하자 🔥
MySQL서버에 내가 실행하고자 하는 쿼리를 전달하면, Query Parser가 쿼리를 파싱하고 전처리기를 거쳐서 Optimizer가 실행계획을 수립한다.
하나의 쿼리를 어떻게 하면 효율적으로 잘 실행할지를 판단하는 것이다.
이렇게 user
테이블과 post
테이블이 있다고 가정해보자.
SELECT * FROM post LEFT JOIN user ON post.user_id = user.id;
그리고 이렇게 쿼리를 실행한다면, 아래와 같은 결과가 나올것이다.
각자 사용하는 mysql 툴에 따라 다르겠지만 내가 사용하고 있는 HeidiSQL
의 경우 쿼리를 선택하고 우클릭을 하면,
이렇게 실행계획을 확인할 수 있다.
하나의 쿼리 문에 대해서 2단계의 실행 계획이 생성이 된 것이다.
이 실행계획이 정확히 어떤 의미인지 자세히 살펴보자.
SELECT문을 구분하는 id값이다. 위의 예시에서는 하나의 SELECT문을 실행하고 있으므로 id는 1로 통일되었다.
말 그대로 SELECT 문의 종류인데, 일반 쿼리인지 서브 쿼리인지 UNION문인지등등으로 select_type이 결정되게 된다.
Derived Table
이라고 한다. 이를 나타내는 select_type
값이 DERIVED
이다.💡 UNCACHEABLE SUBQUERY, UNCACHEABLE UNION 는 사용자 변수나 특정 MySQL 내부 함수를 사용한 경우를 나타내고, 그래서 캐싱을 할 수 없다는 것을 의미한다. 캐싱을 할 수 없다는 것은 성능이 떨어질 가능성이 있다는 것이므로, 유의할 필요가 있다.
기본적으로 테이블 명이 명시되는 컬럼이다. 단, 임시 테이블도 값이 될 수 있는데, 예시로는 아래와 같다.
하나의 테이블의 용량이 너무 클 경우 파티셔닝을 할 수 있는데, 그런 경우에 어떤 파티션에서 실행된 SELECT문인지를 나타낸다.
JOIN의 종류를 나타낸다.
system, const, eq_ref, ref, fulltext, ref_or_null, unique_subquery, index_subquery, range, index_merge, index, ALL 과 같은 종류들이 있는데, ALL은 풀스캔을 하는 것을 나타내는 것이여서 성능이 가장 안좋다.
(system에서 ALL로 갈수록 성능이 안좋다.)
Optimizer가 최적화할 때, 후보로 선정됐던 접근 방법에서 사용되는 인덱스들이다.
Optimizer가 최적화할 때 사용한 인덱스일 경우 그 인덱스가 표시된다.
풀스캔을 했을 때는 NULL이 찍히게 된다.
쿼리 최적화를 할 때, 내가 의도한 인덱스가 잘 찍혔는지 확인해볼 수 있다.
최적화에 선택된 인덱스의 길이이다.
위의 type컬럼에 ref
라고 되어 있을 때, 어떤 컬럼이 조건문에 사용되었는지를 나타낸다.
Optimizer가 스캔해야하는 레코드 수를 예측한 값이다. 실제 레코드 수와 일치하지 않을 수 있다.
필터링되고 남은 레코드 수의 비율이다. 실제 비율과 다를 수도 있다.
Optimizer가 어떻게 동작하는 지를 알려주는 중요한 컬럼이다.
이 또한 종류가 매우 많다..
문장 형태로 되어 있어서 대충 의미를 파악할 수 있긴하다. 😂
에.... 쿼리가 어떻게 실행됐다는 것들이 쭉 열거 되는데, 디버깅이 필요하다면 EXTRA 컬럼을 까보면 좋을 것 같다 ㅎㅎ
이번 글에서는 MySQL 실행 계획에 대해서 알아보았다.
MySQL의 동작 방식이 좀 더 와닿았던 것 같다.
내 하찮은 쿼리를 실행해주느라 MySQL이 고생이 참 많다.. ㅎㅎ;;
그럼 20000...