[데이터베이스] N+1 문제_Mybatis
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 문제가 발생하지 않는다.
참고