반응형
![1](https://blog.kakaocdn.net/dn/oYdoV/btrpWyloxH9/TqDQByToPR3GzK7i86tQF0/img.png)
실제 발생했던 쿼리)
SELECT i.id, i.titles[1] title, i.icon_url, ic.user_id
FROM institutions i
INNER JOIN interested_corporations ic ON ic.institution_id = i.id AND ic.deleted_at IS NULL
INNER JOIN users u ON ic.user_id = u.id AND u.deleted_at IS NULL
INNER JOIN vouchers v ON v.user_id = u.id AND v.deleted_at IS NULL AND v.course_id = ?
WHERE i.priority > ?
AND i.type = ?
AND NOT (u.is_admin = true AND i.id = ?);
실행 계획
실행 계획을 돌려보면 다음과 같은 결과가 나온다
Nested Loop (cost=388.26..5059.62 rows=2 width=556) (actual time=296.998..37097.171 rows=786 loops=1)
Join Filter: (ic.user_id = v.user_id)
-> Nested Loop (cost=387.82..3653.85 rows=726 width=571) (actual time=28.904..10185.405 rows=33165 loops=1)
Join Filter: ((NOT u.is_admin) OR (i.id <> 1))
Rows Removed by Join Filter: 9
-> Hash Join (cost=387.40..2624.89 rows=732 width=567) (actual time=25.787..93.559 rows=33218 loops=1)
Hash Cond: (ic.institution_id = i.id)
-> Seq Scan on interested_corporations ic (cost=0.00..1929.74 rows=117208 width=8) (actual time=1.511..45.261 rows=117116 loops=1)
Filter: (deleted_at IS NULL)
Rows Removed by Filter: 75
-> Hash (cost=386.88..386.88 rows=42 width=563) (actual time=24.243..24.244 rows=45 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 14kB
-> Seq Scan on institutions i (cost=0.00..386.88 rows=42 width=563) (actual time=2.377..24.221 rows=45 loops=1)
Filter: ((priority > 3) AND ((type)::text = 'CORPORATION'::text))
Rows Removed by Filter: 6789
-> Index Scan using users_pkey on users u (cost=0.42..1.39 rows=1 width=5) (actual time=0.302..0.303 rows=1 loops=33218)
Index Cond: (id = ic.user_id)
Filter: (deleted_at IS NULL)
Rows Removed by Filter: 0
-> Index Scan using vouchers_user_id_index on vouchers v (cost=0.43..1.92 rows=1 width=4) (actual time=0.800..0.810 rows=0 loops=33165)
Index Cond: (user_id = u.id)
Filter: ((deleted_at IS NULL) AND (course_id = 19812))
Rows Removed by Filter: 17
Planning time: 92.381 ms
Execution time: 37097.408 ms
총 37초가 수행되었다.
상세하게 보면, 테이블: vouchers
에서 27초, users
에서 10초가 수행되었다.
![2](https://blog.kakaocdn.net/dn/dz7O5D/btrpYnDYW2V/QASUNSGZ6bgkZ1PiKAwnoK/img.png)
근데 여기서 그래프를 보면 vouchers는 인덱스 스캔 인데도 28초나 걸렸다.
왜그럴까?
이유는 인덱스로 걸러지고 추가로 필터링 해야할 데이터가 아직도 많기 때문이다.
![3](https://blog.kakaocdn.net/dn/bSYF9u/btrp3Wx3xww/lAmJdGYXF74KKp3OzN7Gx1/img.png)
문제가 되는 쿼리는 아래 1줄 영역이다
INNER JOIN vouchers v ON v.user_id = u.id AND v.deleted_at IS NULL AND v.course_id = ?
살펴보면 user_id 인덱스 (vouchers_user_id_index
) 만 적용되었다.
그래서 user_id 인덱스로 적용되어 나온 결과물을 1건씩 filter 처리하면서 걸러내는 작업을 하다보니 수십초가 걸렸다
해결책
RDBMS는 쿼리 사용시 한 테이블에는 1개의 인덱스만 적용 된다.
즉, course_id 인덱스와 user_id 인덱스가 각각 있을 경우 그 중에서 가장 효율적인 인덱스 1개만 적용 된다.
이럴 경우 복합 컬럼 인덱스를 잡아야 한다.
복합컬럼의 경우 조인 컬럼 -> 조건별 컬럼으로 생성한다.
현재의 경우 조인 컬럼 (user_id
) → 조건컬럼 (course_id, deleted_at
)으로 잡는다.
즉, 신규 인덱스는 다음과 같이 생성된다.
create index vouchers_user_id_course_id_index on vouchers (user_id, course_id, deleted_at);
이후 다시 쿼리를 수행하고 실행계획을 보면?
![4](https://blog.kakaocdn.net/dn/GPbHc/btrp3VFVcja/y9rJbMIsbnzSnauiSHAU7k/img.png)
![5](https://blog.kakaocdn.net/dn/ci4UzN/btrp0IANFZ3/m2asEr7QvzUxRyOdvASwv0/img.png)
총 수행시간이 0.093초 (93.ms)로 단축된 것을 확인할 수 있다.
반응형