home
Where 절에 exists 넣기
만약 인증을 한 사용자만 쿼리해오고 싶으면 어떻게 할까요?
가장 간단한 방법은 JOIN을 하는 것이 생각이 납니다.
SELECT
*
FROM
users u JOIN verify v ON u.id = v.user_id
인증 정보와 같이 1:1 상황이라면 간단하게 INNER JOIN을 사용하면 되겠지만 만약 글이 여러 개 있는 사용자만 불러오고 싶을 때도 JOIN을 써야 할까요?
SELECT
*
FROM
users u JOIN articles a ON u.id = a.user_id
GROUP BY
u.id
이렇게하면 글이 있는 user들을 불러올 수 있지만, 조인을 하는 과정에서 불필요한 JOIN이 정말 많이 일어날 것 같습니다. 실제로 해당 쿼리의 실행 속도는 다음과 같습니다.
(actual time=1116.316..1116.344 rows=63 loops=1)
해당 쿼리는 EXISTS 서브 쿼리로 성능 향상을 시킬 수 있습니다.
SELECT
*
FROM
users u
WHERE
EXISTS (SELECT 1 FROM article a WHERE u.id = a.user_id)
GROUP BY
u.id
해당 쿼리는 다음과 같이 동작합니다.
- 메인 쿼리에서 각 행을 하나씩 처리합니다.
- 각 행에 대해 EXISTS 내부의 서브쿼리를 실행합니다.
- 서브쿼리가 하나 이상의 결과를 반환하면 TRUE, 아무 결과도 반환하지 않으면 FALSE입니다.
- TRUE인 경우만 메인 쿼리의 결과에 포함됩니다.
해당 쿼리의 실행 속도는 다음과 같았습니다.
(actual time=164.810..177.526 rows=63 loops=1)
EXISTS 서브쿼리가 JOIN보다 약 6배 빠른 결과를 보여주었는데, 왜 이러한 차이가 발생하는 걸까요?
EXISTS 서브 쿼리가 더 빠른 이유
데이터 처리량 감소
JOIN은 모든 매칭 레코드를 중간 결과로 생성합니다. 사용자 한 명당 여러 글이 있다면, 중간 결과는 매우 커질 수 있습니다.
- 사용자 100명, 각 사용자당 평균 50개 글
- JOIN: 최대 5,000개 행의 중간 결과 생성
- EXISTS: 사용자 100명에 대해서만 처리
조기 중단(Short-circuit) 효과
위에서 쿼리 동작에서 봤듯이, EXISTS는 첫 번째 일치하는 레코드를 찾으면 즉시 TRUE를 반환하고 더 이상 검색하지 않습니다. 사용자가 글을 많이 작성했더라도 하나만 찾으면 됩니다.
마무리
1:N 관계에서 N의 수가 많을수록 EXISTS의 성능 이점이 더 커집니다. 불필요하게 JOIN을 했던 쿼리들이 있으면 최적화를 해보는 건 어떨까요?