본문 바로가기
DBMS || SQL_MAPPER/MYSQL

간단하게 알아보는 쿼리 실행계획의 중요성

by DanteMustDie 2023. 11. 3.
728x90

이번 시간에는 같은 결과를 가져오더라도 쿼리 짜는 퀄리티에 따라 비용(cost)이 바뀌는 것에 대해 이야기 하고자 한다.

아주 기초적인 예시와 내용으로 작성 하였으므로 이해에 어려움이 발생하진 않을 것이라 본다.

위와 같은 사용자 정보를 담는 user_info 테이블이 있다고 가정을 하자.

이 테이블에는 더미데이터 회원정보가 1천건이 저장되어 있다.

 

내가 여기서 통계를 내기 위해 전체 회원수, 월 가입자 수, 6개월 이상 미접속자의 수3개를 조회하고자 한다.

 

쿼리 초보자의 입장에선 SELECT를 통해 세가지 값을 가져와야 한다는 것을 인지는 할텐데

문법상 select "column_name" from "table" 인 것을 생각하여 다음과 같이 조회를 했다고 가정해보자.

1
2
3
4
5
6
-- 전체 회원수
SELECT COUNT(*FROM user_info) AS total_users;
-- 월 가입자
SELECT COUNT(*FROM user_info WHERE MONTH(created_at) = MONTH(CURRENT_DATE()) AND YEAR(created_at) = YEAR(CURRENT_DATE())) AS monthly_active_users;
-- 6개월 이상 미접속자, 수정일
SELECT COUNT(*FROM user_info WHERE modified_at < DATE_SUB(CURDATE(), INTERVAL 6 MONTH)) AS inactive_users;
cs

먼저 각각의 데이터를 조회하는 쿼리를 개별적으로 작성했다.

이것을 이제 하나의 row로 보여주기위해 아래와 같이 작성했다고 가정하자.

1
2
3
4
5
6
7
8
9
SELECT
    total_users.total_users,
    monthly_active_users.monthly_active_users,
    inactive_users.inactive_users
FROM
    (SELECT COUNT(*) AS total_users FROM user_info) AS total_users,
    (SELECT COUNT(*) AS monthly_active_users FROM user_info WHERE MONTH(created_at) = MONTH(CURRENT_DATE()) AND YEAR(created_at) = YEAR(CURRENT_DATE())) AS monthly_active_users,
    (SELECT COUNT(*) AS inactive_users FROM user_info WHERE modified_at < DATE_SUB(CURDATE(), INTERVAL 6 MONTH)) AS inactive_users
    ;
cs

 

쿼리를 확인하면 조회 결과가 잘 나온다.

이제 여기서 SELECT 앞에 쿼리 실행계획을 확인하는 EXPLAIN을 입력하여 같은 쿼리를 다시 실행해보자.

 

다음과 같이 코스트가 6개로 잡힌다.

문법상으로 보자면 FROM절 다음엔 테이블이 와야하고, 테이블은 데이터를 구성하는 집합이므로 문맥상 틀리진 않았다.

그러나, FROM절 다음에 나온 SELECT가 3개 사용되어 같은 테이블에 3번 요청, 결과를 가져온 꼴이 되었으므로 

결과적으로 3+3 -> 6이 되었다.

1
2
3
4
5
SELECT
    (SELECT COUNT(*FROM user_info) AS total_users,
    (SELECT COUNT(*FROM user_info WHERE MONTH(created_at) = MONTH(CURRENT_DATE()) AND YEAR(created_at) = YEAR(CURRENT_DATE())) AS monthly_active_users,
    (SELECT COUNT(*FROM user_info WHERE modified_at < DATE_SUB(CURDATE(), INTERVAL 6 MONTH)) AS inactive_users
;
cs

이번엔 SELECT 절의 "COLUMN_NAME" 부분을 서브쿼리로 하여 각각 3개의 건을 가져오는 방법으로 바꾼 쿼리다.

결과는 위와 같으며 실행계획을 보면 가상의 테이블을 조회하는 것으로 취급하여 1개, 서브쿼리로 데이터를 뽑아오는 것으로 3개로 되어 위의 6개보다 2개 줄은 4개이다.

1
2
3
4
5
SELECT
    COUNT(*) AS total_users,
    SUM(CASE WHEN MONTH(created_at) = MONTH(CURRENT_DATE()) AND YEAR(created_at) = YEAR(CURRENT_DATE()) THEN 1 ELSE 0 END) AS monthly_active_users,
    SUM(CASE WHEN modified_at < DATE_SUB(CURDATE(), INTERVAL 6 MONTH) THEN 1 ELSE 0 END) AS inactive_users
FROM user_info;
cs

다음 쿼리는 SUM과 CASE 같은 추가 함수를 사용하여 즉시 조회가 가능하도록 수정을 한 쿼리이다.

결과는 마찬가지로 동일하며 실행계획 코스트가 1개만 잡힌다.

 

그러므로 쿼리는 작성을 잘 해야 하며, 작성하고나서 속도 개선이 필요하다 판단되면 실행계획을 확인하여 줄여가는 과정을 거쳐야 한다.

반응형