profile image

L o a d i n g . . .

728x90

Spring Boot JPA를 사용하던 중 실제로 어떤 쿼리가 실행되는지 문뜩 궁금해졌습니다. 그래서 MySQL에서 수행되는 실제 쿼리를 살펴봤는데요, 이번 포스팅을 통해 공유해보고자 합니다. 

MySql 쿼리 실행 기록 

MySql은 쿼리의 실행 기록을 확인할 수 있는 방법이 있습니다. 아래와 같은 쿼리를 실행시키면 general_log 값을 알 수 있습니다. general_log가 OFF일 경우 쿼리 실행 기록이 남지 않습니다. 만약 general_log가 ON이라면 쿼리가 실행될 때마다 general_log_file에 쿼리 실행 이력이 저장됩니다. 

MySql general_log variable

그럼 general_log를 ON으로 설정하고 쿼리의 실행 이력이 어떻게 저장되는지 살펴보겠습니다. 

※ 주의 ) general_log를 ON 값으로 설정하고 쿼리를 실행시키면 local file system에 쌓이는 로그의 크기가 기하급수적으로 늘어날 수 있습니다. 따라서 general_log를 ON 하고 필요한 작업이 완료되면 OFF 값으로 되돌리는 것이 권고됩니다. 

 

general_log를 ON으로 설정하는 쿼리

쿼리가 실행될 때 로그에 어떻게 쌓이는지 확인해보기 위해 다음과 같은 명령어로 실시간으로 쌓이는 로그를 확인해보겠습니다. 

tail -f /opt/homebrew/var/mysql/AL01983658.log

다음과 같은 쿼리를 실행하면 로그는 다음과 같이 쌓입니다. 

예시 쿼리
예시 로그

쿼리 실행 이력 select * from menu 가 기록되는 것을 확인할 수 있습니다. 추가로 저는 Datagrip이라는 DBMS 툴을 사용 중인데요, Datagrip에서 limit 없이 select문을 수행하면 자동으로 Datagrip이 limit을 추가하는 걸 확인할 수 있습니다. 데이터베이스에서 로그를 확인할 수 있는 방법에 대해 알아봤으니 spring boot JPA에 의해 MySQL에서 실행되는 쿼리가 어떤지 살펴보겠습니다. 

Spring Boot JPA는 어떤 쿼리가 실행될까? 

사용하는 코드는 다음 링크에서 확인할 수 있습니다. 

https://github.com/seonwoo960000/spring-boot-jpa-mysql-log-query-example

 

GitHub - seonwoo960000/spring-boot-jpa-mysql-log-query-example

Contribute to seonwoo960000/spring-boot-jpa-mysql-log-query-example development by creating an account on GitHub.

github.com

Spring Boot JPA를 통해 사용되는 쿼리는 어떻게 실행되는지 살펴보겠습니다. Entity와 관련 Repository는 다음과 같이 설정했습니다. 

@Getter
@Builder
@Entity(name = "member")
@AllArgsConstructor
@NoArgsConstructor
public class MemberEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long pid;

    @Column(nullable = false, unique = true, length = 30)
    private String username;

    @Column(nullable = false, length = 100)
    private String name;

}
public interface MemberRepository extends JpaRepository<MemberEntity, Long> {
}

이를 편리하게 접근할 수 있게 Controller도 생성하겠습니다. 

@RestController
@RequiredArgsConstructor
@RequestMapping("api")
public class MemberController {

    private final MemberRepository memberRepository;

    @GetMapping("members")
    public List<MemberEntity> findAllMembers() {
        return memberRepository.findAll();
    }

    @GetMapping("members/{id}")
    public MemberEntity findById(@PathVariable Long id) {
        return memberRepository.findById(id)
                               .orElse(null);
    }

    @PostMapping("members")
    public MemberEntity save(@RequestParam String username,
                             @RequestParam String name) {
        return memberRepository.save(MemberEntity.builder()
                                                 .username(username)
                                                 .name(name)
                                                 .build());
    }
}

 

그럼 위의 controller endpoint를 호출했을 때 각각 어떤 MySQL 쿼리가 호출되는지 살펴보겠습니다. 

findAll() 

JPA findAll()의 실제 MySql 실행 쿼리

JPA에서 자동으로 설정해주는 몇 가지 특징을 살펴볼 수 있습니다. 먼저 transaction read only를 실행함으로써 해당 세션의 transaction이 read only임을 알립니다. 그다음 autocommit을 비활성화하고 요청된 쿼리문을 실행 후 직접 commit을 실행시킵니다. 

findById(id) 

JPA findById()의 실제 MySql 실행 쿼리

findAll()과 거의 유사하고 id를 통한 검색 조건만 추가됐습니다. 

save(MemberEntity)

JPA save()의 실제 MySql 실행 쿼리

findAll() 또는 findById()와 다르게 transaction의 read only를 설정하지 않는 것을 볼 수 있습니다.

saveWithDifferentIsolationLevel(name, username) 

이번에는 JPA에서 자동으로 생성해주는 메서드가 아닌 제가 직접 쿼리를 생성해서 사용해보겠습니다. 

@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@Query(value = "insert into Member (name, username) values (:name, :username)",
        nativeQuery = true)
int saveWithDifferentIsolationLevel(@Param("name") String name,
                                    @Param("username") String username);

위 메서드는 isolation_level을 READ_UNCOMMITTED로 설정해서 쿼리를 실행시킵니다. 해당 메서드를 수행하면 MySql은 다음과 같은 로그가 생성됩니다.

saveWithDifferentIsolationLevel의 실제 MySql 실행 쿼리

MySql은 쿼리 수행 전 해당 세션의 isolation level을 READ UNCOMMITTED로 수정하는 것을 확인할 수 있습니다. 

결론 

위의 로깅 기능을 활용하면 Spring Boot 뿐만 아니라 다른 프레임워크를 사용해서 데이터베이스에 쿼리를 날릴 때 실제 쿼리가 어떻게 동작하는지 살펴볼 수 있습니다. 하지만 로그의 크기가 커질 수 있음에 주의해서 사용해야 할 것 같습니다. 

로그의 크기가 비정상적으로 커지지 않도록 OFF 설정

728x90
복사했습니다!