JPA에서 연관된 엔티티를 지연 로딩(LAZY)으로 설정해두면 발생하는 대표적인 퍼포먼스 이슈이다.
| 문제점 | 설명 |
|---|---|
| 쿼리 과다 발생 | 원래는 한 번에 처리할 수 있는 데이터를 위해 수십~수백 개의 쿼리가 추가로 실행됨 |
| DB 부하 증가 | 많은 쿼리가 동시에 실행되면 DB 성능이 급격히 저하됨 |
| 응답 지연 | 클라이언트 입장에서는 응답 시간이 길어짐 → UX 악화 |
| 트래픽 비효율 | 서버-DB 간 트래픽이 과도하게 증가 |
| 스케일 문제 | 데이터가 많아질수록 성능이 기하급수적으로 떨어짐 |
| 디버깅 어려움 | 문제를 코드에서 쉽게 발견하지 못하고, 실서비스에서 터질 때까지 모를 수 있음 |
예를 들어, 목록을 가져오는 수행을 한다.(1)
그 다음 각 팀의 멤버를 반복해서 가져오면(N)
→ 총 1+N개의 쿼리가 실행된다.
"하나의 조회에 대해 연관된 엔티티 수만큼 추가 쿼리가 발생하는 문제"이다.
SELECT * FROM users; -- 1번
SELECT * FROM orders WHERE user_id = 1; -- 2번
SELECT * FROM orders WHERE user_id = 2; -- 3번
...
SELECT * FROM orders WHERE user_id = 100; -- 101번
연관된 객체를 실제로 사용할 때까지 쿼리가 지연되기 때문이다.
JOIN FETCH 문법을 사용하여 연관된 엔티티를 한 번에 로딩@Query("SELECT t FROM Team t JOIN FETCH t.members")
List<Team> findAllWithMembers();DISTINCT 키워드로 해결 가능@EntityGraph(attributePaths = {"members"})
List<Team> findAll();JPA/Hibernate가 지연 로딩할 때 IN 쿼리로 한 번에 가져오도록 유도
예시 설정 (application.yaml):
spring:
jpa:
properties:
hibernate.default_batch_fetch_size: 100
예시 동작:
SELECT * FROM member WHERE team_id IN (1,2,3,...,100)장점:
주의점:
| 방법 | 설명 |
|---|---|
| Fetch Join | 연관된 엔티티를 한 번에 가져오는 JPQL |
| EntityGraph | 애노테이션으로 fetch join 효과 주기 |
| Batch Size 설정 | 여러 LAZY 객체를 한 번에 IN 쿼리로 조회 |
N+1 문제는 지연 로딩된 연관 데이터를 반복 조회하며 쿼리가 과도하게 발생하는 문제로, 이를 해결하기 위해 Fetch Join, EntityGraph, Batch Size 설정 등을 사용해 연관 데이터를 효율적으로 미리 로딩하거나 묶어서 불러온다.
🧾 격리성(Isolation)이란?
ACID는 데이터베이스 트랜잭션이 신뢰성 있게 처리되도록 보장하는 4가지 핵심 속성이다.
그 중 격리성은 동시에 실행 중인 트랜잭션들이 서로의 중간 상태를 보거나 영향을 주지 않도록 보장한다.
| 문제 | 설명 |
|---|---|
| Dirty Read | 다른 트랜잭션이 커밋하지 않은 값을 읽음 |
| Non-repeatable Read | 같은 SELECT인데 값이 바뀌는 경우 |
| Phantom Read | 같은 조건인데 새로 생긴 행이 추가됨 |
| 수준 | 설명 | 막을 수 있는 문제 |
|---|---|---|
| READ UNCOMMITTED | 다 읽음 | 없음 |
| READ COMMITTED | 커밋된 것만 읽음 | Dirty Read |
| REPEATABLE READ | 같은 SELECT는 같은 결과 | Non-repeatable Read |
| SERIALIZABLE | 가장 엄격, 완전한 고립 | Phantom Read까지 다 막음 |
격리 수준이 높을수록: 데이터 충돌은 적지만, 성능은 떨어짐
깔끔한 정리 👍 잘 읽고 갑니당