2022. 5. 23. 22:42ㆍ데이터베이스
지난 프로젝트동안 감사하게도 무려 카카오에 재직중이신 Jack에게 코드리뷰를 받을 기회가 있었습니다.
받았던 코멘트 중, 기능 구현에 급급해 당시에는 넘어갔던 내용 중 다시 살펴보니 흥미로운 내용을 공유해보려고 합니다.
음식상품 데이터를 랜덤으로 추출하는 API를 클라이언트쪽에 제공하고 있었습니다.
초기 데이터를 선택해서 뿌려주는 로직이라 위와 같이 ORDER BY RAND() LIMIT 3 이라는 쿼리문을 작성했고, 잘 작동했습니다.
그런데 위 쿼리는 큰 문제가 있습니다. ORDER BY 의 조건에 RAND()라는 함수가 걸려있어 스토리지 엔진이 비효율적으로 작동하기 때문입니다.
RAND() 함수는 쿼리 실행 순간에 레코드에 각각 임의의 값을 할당한 후에 그 할당된 값으로 정렬을 수행하게 됩니다. 비용이 아주 큰 연산이 됩니다.
더 자세하게는, ORDER BY 조건에 인덱스를 활용할 수 있는 조건이 아닌 모든 조건은 비효율적으로 작동하게 됩니다.
다음 예를 보면,
@Entity
@Data
public class Item_rand {
@Id @GeneratedValue
private Long id;
private String name;
}
@Repository
public interface RandRepository extends CrudRepository<Item_rand, Long> {
@Query(value = "select * from item order by rand()", nativeQuery = true)
List<Item_rand> findAll();
}
엔티티 클래스가 있고 해당 엔티티를 랜덤으로 가져오는 메서드가 있습니다.
이 로직에서 만약 item 테이블의 레코드가 1억개가 넘는다면 어떻게 될까요?
1억개의 데이터에 1억번의 데이터 할당과 정렬을 수행하게 됩니다. 사실상 실무에서 쓸 수 없는 코드라는 짐작이 가실 겁니다.
이 문제를 개선해보겠습니다.
@Entity
@Data
public class Item_rand_improved {
@Id @GeneratedValue
private Long id;
private String name;
private int randId; // 여기에 인덱스 생성
}
테이블(엔티티)에 rand_id 라는 속성을 추가했습니다.
테이블에 데이터를 삽입할 때, rand_id 속성에 랜덤한 값이 들어가도록 설정해줍니다. (대신 속성이 int형이므로 100000000을 곱한 값을 삽입합니다)
-> 각 레코드가 랜덤한 값을 가집니다
그럼 이제 ORDER BY RAND() 에서 랜덤값을 레코드의 개수만큼 할당하는 문제는 해결되었습니다. 이미 랜덤값이 할당되어 있으니까요.
쿼리도 개선해보겠습니다
@Repository
public interface RandRepository extends CrudRepository<Item_rand, Long> {
@Query(value = "select * from item_rand_improved where rand_id > FLOOR((RAND()*100000000)) order by rand_id limit 3", nativeQuery = true)
List<Item_rand_improved> findAll_improved();
}
rand()에 또 한번 100000000을 곱한 값을 where 조건에 넣어 필터링한 후, order by 에 아까 삽입한 인덱스가 있는 rand_id 컬럼을 조건으로 넣어줍니다.
완성!
'데이터베이스' 카테고리의 다른 글
InnoDB의 레코드락에 대한 궁금점 (1) | 2022.07.15 |
---|---|
MySQL8.0 Error Code: 1071. Specified key was too long 해결법 (1) | 2022.07.15 |
[MySQL8.0] B-Tree 인덱스 사용에 영향을 미치는 요소 (0) | 2022.06.17 |
서비스로직과 트랜잭션 분리 [SPRING] (1) | 2022.05.12 |
[Real MySQL] 슬로우 쿼리 로그 확인 (0) | 2022.05.12 |