데이터베이스 엔진은 쿼리를 수행할 때 어떤 방식을 사용하고 어떤 순서로 쿼리를 수행 할지에 대한 계획을 세우게 되는데,
이것을 쿼리 실행 계획(Query execution plan) 또는 쿼리 플랜(Query plan) 이라고 부른다.
PostgreSQL과 MySQL 모두 EXPLAIN 이라는 SQL구문을 이용해 쿼리 플랜을 확인할 수 있다.
그리고 성능 분석을 할 때는 보통 ANALYZE 문까지 붙여서 실행 시간을 포함한 구체적인 실행 계획을 분석한다.
💁♂️ EXPLAIN
EXPLAIN ANAYLIZE
다음과 같은 쿼리를 개선하고자 한다.
EXPLAIN select
t1_0.id,
c1_0.id,
c1_0.created_at,
c1_0.modified_at,
c1_0.name,
c1_0.type,
c2_0.thread_id,
c2_0.id,
c2_0.message,
c2_0.user_id,
t1_0.created_at,
e1_0.thread_id,
e1_0.id,
e1_0.body,
e1_0.user_id,
m1_0.thread_id,
m1_0.user_id,
m1_0.created_at,
m1_0.modified_at,
t1_0.message,
t1_0.modified_at,
t1_0.user_id
from
thread t1_0
left join
channel c1_0
on c1_0.id=t1_0.channel_id
left join
thread_emotion e1_0
on t1_0.id=e1_0.thread_id
left join
comment c2_0
on t1_0.id=c2_0.thread_id
left join
thread_mention m1_0
on t1_0.id=m1_0.thread_id
left join
thread_mention m2_0
on t1_0.id=m2_0.thread_id
where
exists(select
1
from
thread_mention m3_0
where
m3_0.user_id=1
and t1_0.id=m3_0.thread_id)
order by
m2_0.created_at desc;


위와 같은 결과가 나온다.
ANALYZE 를 추가하면

위와 같은 결과가 나온다.
left join
thread_mention m1_0
on t1_0.id=m1_0.thread_id
left join
thread_mention m2_0
on t1_0.id=m2_0.thread_id
같은 테이블이 두 번 JOIN 되고 있는 것을 확인할 수 있다.
하나를 제거하고 결과를 보면


select
t1_0.id,
c1_0.id,
c1_0.created_at,
c1_0.modified_at,
c1_0.name,
c1_0.type,
#c2_0.thread_id, # join on t1_0.id = c2_0.thread_id -> t1_0.id 중복
c2_0.id,
c2_0.message,
c2_0.user_id,
t1_0.created_at,
#e1_0.thread_id, # join on t1_0.id = e1_0.thread_id -> t1_0.id 중복
e1_0.id,
e1_0.body,
e1_0.user_id,
#m1_0.thread_id, # join on t1_0.id = m1_0.thread_id
m1_0.user_id,
m1_0.created_at,
m1_0.modified_at,
t1_0.message,
t1_0.modified_at,
t1_0.user_id
from
주석에 설명한 이유로 불필요한 필드를 삭제해 비용을 줄였다.
where
exists(select
1
from
thread_mention m3_0
where
m3_0.user_id=1
and t1_0.id=m3_0.thread_id)
위와 같은 서브쿼리는 Using temporary; Using filesort 풀스캔을 돌리기 때문에 효율적이지 못하다.
위의 쿼리는 m1_0.user_id = 1 로 대체할 수 있다.
결과는


m1_0 테이블의 정렬(ORDER BY m1_0.created_at DESC)에서 파일 정렬이 발생하므로 user_id와 created_at에 복합 인덱스를 생성할 수 있다.
CREATE INDEX idx_user_id_created_at ON thread_mention (user_id, created_at DESC);
결과는 다음과 같다.

-- CREATE INDEX idx_user_id_created_at ON thread_mention (user_id, created_at DESC);
EXPLAIN select
t1_0.id,
c1_0.id,
c1_0.created_at,
c1_0.modified_at,
c1_0.name,
c1_0.type,
c2_0.id,
c2_0.message,
c2_0.user_id,
t1_0.created_at,
e1_0.id,
e1_0.body,
e1_0.user_id,
m1_0.user_id,
m1_0.created_at,
m1_0.modified_at,
t1_0.message,
t1_0.modified_at,
t1_0.user_id
from
thread t1_0
left join
channel c1_0
on c1_0.id=t1_0.channel_id
left join
thread_emotion e1_0
on t1_0.id=e1_0.thread_id
left join
comment c2_0
on t1_0.id=c2_0.thread_id
left join
thread_mention m1_0
on t1_0.id=m1_0.thread_id
where m1_0.user_id = 1
order by
m1_0.created_at desc;
어렵다 어려워
웹서핑부터 질의까지 이래저래 힘들었지만 어느정도 개선이 된 것 같다.
틀렸으면 나가죽어버려야지
화이팅!