CS 지식/데이터베이스

[데이터베이스] N+1 문제_Mybatis

ghan2 2024. 10. 6. 21:53

 

N+1 문제란?

N+1 문제는 주로 ORM(Object-Relational Mapping) 프레임워크를 사용할 때 발생하는 성능 문제로, 한 번의 쿼리로 N개의 객체를 가져온 후, 각 객체와 관련된 데이터를 조회하기 위해 추가로 N번의 쿼리가 실행되는 상황을 의미한다. 이는 성능에 심각한 영향을 미칠 수 있으며, 특히 다수의 데이터베이스 요청이 발생할 경우 성능 저하가 두드러지게 나타난다.

 

N+1 문제는 언제 나타날까?

예를 들어, 사용자(User) 테이블과 게시글(Post) 테이블이 1:N 관계에 있다고 하자.

사용자를 조회한 후, 각 사용자의 게시글을 따로 조회하는 쿼리를 작성한다면, 여기서 N+1 문제가 발생하게 된다. 

CREATE TABLE User (
    user_id INT PRIMARY KEY,
    name VARCHAR(100)
);
CREATE TABLE Post (
    post_id INT PRIMARY KEY,
    user_id INT,
    content VARCHAR(255),
    FOREIGN KEY (user_id) REFERENCES User(user_id)
);

 

사용자의 정보와 게시글을 조회하기 위해서 아래와 같은 쿼리를 날린다고 하자.

// 한 번의 쿼리
<select id="selectAllUsers" resultType="User">
    SELECT * FROM User;
</select>

// N 번의 쿼리 --> N+1 문제 발생
<select id="selectPostsByUserId" resultType="Post">
    SELECT * FROM Post WHERE user_id = #{userId};
</select>

 

위의 쿼리를 사용하면 먼저 모든 사용자 데이터를 가져온 후 

SELECT * FROM User;

 

각 사용자에 대해 selectPostsByUserId를 호출하여 해당 사용자의 게시글을 가져온다. 이 과정에서 사용자마다 쿼리가 실행된다. 

SELECT * FROM Post WHERE user_id = 1;
SELECT * FROM Post WHERE user_id = 2;
SELECT * FROM Post WHERE user_id = 3;
SELECT * FROM Post WHERE user_id = 4;
SELECT * FROM Post WHERE user_id = 5;

이 경우, 총 1번의 사용자 조회 쿼리와 5번의 게시글 조회 쿼리가 발생하게 되어 1 + N(5) 번의 쿼리가 발생한다. 

 

뭐가 문제일까?

사용자가 많아질수록, 즉 N이 커질수록 데이터베이스에 전송되는 쿼리의 수가 급격히 늘어난다. 

이로 인해 성능이 크게 저하될 수 있다. 특히, 데이터가 많고 네트워크 레이턴시가 높은 환경에서 문제가 심각해진다. 

 

해결 방법

이를 해결하려면 한 번의 쿼리로 모든 데이터를 가져오도록 JOIN을 사용하거나, collection을 이용해 한 번에 필요한 데이터를 가져오는 방식으로 최적화할 수 있다. 

 

하지만 <collection>태그, <assosiation> 태그를 사용할 때 'select' 속성을 사용한다면 N+1쿼리 문제를 가져오게 된다.

<select id="findUsersWithPosts" resultMap="userPostMap">
    SELECT 
        U.user_id AS userId, 
        U.name AS userName, 
        P.post_id AS postId, 
        P.content AS postContent
    FROM 
        User U
    LEFT JOIN 
        Post P ON U.user_id = P.user_id
</select>
<resultMap id="userPostMap" type="User">
    <id property="userId" column="userId"/>
    <result property="userName" column="userName"/>

    <collection property="posts" ofType="Post">
        <id property="postId" column="postId"/>
        <result property="content" column="postContent"/>
    </collection>
</resultMap>

resultMap을 통해 User 객체에 게시를 목록을 collection으로 매핑한다. 

 

이렇게 하면 한 번의 쿼리로 모든 데이터를 가져오기 때문에 N+1 문제가 발생하지 않는다. 

 


참고

https://mybatis.org/mybatis-3/ko/sqlmap-xml.html

https://deveric.tistory.com/74