제목: "JPA exists 쿼리 성능 개선"
작성자: tistory.com(jojoldu)
작성자 수정일: 2020. 8. 6
링크: https://jojoldu.tistory.com/516
작성일: 2022년7월12일
Spring Data Jpa를 사용하다보면 해당 조건의 데이터가 존재하는지 확인 하기 위해 exists
쿼리가 필요할 때가 있다.
간단한 쿼리의 경우엔 아래와 같이 메서드로 쿼리를 만들어서 사용한다.
boolean existsByName(String name);
복잡해지면 메서드명으로만 쿼리를 표현하기가 어려워진다
조건문이 3개 이상 이거나, 필드명이 너무 길거나 조건문 자체가 복잡한 등등
그래서 이런 경우엔 보통 Spring Data Jpa의 JPQL @Query
를 사용한다
다만 이 경우에 JPQL
에서 select의 exists를 지원하지 않는다 ( select exists)
단 Where의 exists는 지원한다.
exists
를 사용할 수 없기 때문에 아래와 같이 count 쿼리를 사용할 수 있다.
@Query("SELECT COUNT(o.id) > 0 " +
"FROM Order o " +
"WHERE o.txDate =:txDate")
boolean exists(@Param("txDate") LocalDate txDate);
하지만 이 방식은 성능상 이슈가 있다.
어떤 이슈가 있는지 확인해보자
SQL로 직접 쿼리를 날려서 성능을 비교해보면 차이를 느낄 수 있따.
5600만건 정도 쌓여있는 테이블을 기준으로 count
와 exsists
를 수행해보면 다음의 성능 차이를 확인할 수 있다.
거의 2배에 가까운 성능 차이를 확인할 수 있다.
데이터가 더 늘면 차이는 더 벌어질것이다.
왜 이렇게 성능 차이가 날까?
이는 exists
는 첫번째 결과에서 바로 true 로 리턴하지만, count의 경우에는 N(전체 데이터)를 확인해봐야 하기 때문에 당연하게 성능 차이가 발생한다.
위 결과로 count
를 쓰면 억울할 것이다.
QueryDSL exists를 보면 fetchCount
를 사용하지 않고 fetchFirst()
를 사용함을 알 수 있다.
fetchFirst()
:limit(1).fetchOne()
이다.
public Boolean exist(Long bookId) {
Boolean result = queryFactory
.selectOne()
.from(book)
.where(book.id.eq(bookId))
.exists();
return result;
}
즉, exists
가 필요한 경우엔 JQPL의 count
보다 QueryDSL
의 exists
를 사용하자
public Boolean exist(Long bookId) {
Integer fetchOne = queryFactory
.selectOne()
.from(book)
.where(book.id.eq(bookId))
.fetchFirst(); // limit 1
return fetchOne != null; // 1개가 있는지 없는지 판단 (없으면 null이라 null체크)
}
fetchFirst()
는 내부적으로limit(1).fetchOne()
로 되어있다.
즉, exists
가 필요한 경우엔 limit 1로 대체가능하다.
메서드 쿼리의 exists는 아래와 같이 limit으로 쿼리 최적화를 내부적으로 하고 있다.
간단한 조건의 exists
가 필요할 때는 메서드 쿼리로 구현해도 될 듯하다.
count
는 exists
에 비해 성능상 안좋다
@Query
와 QueryDSL에서는 select exists
를 사용할 수가 없다.그래서 select exists
를 limit 1
로 대체해서 사용한다.
단, JpaRepository
의 메소드 쿼리에선 내부적으로 limit 1
를 사용하고 있어서 성능상 이슈가 없다.