우당탕탕
[동시성 제어 2편] 비관적 락(Pessimistic Lock) - JPA 스프링으로 경험해보는 실전 가이드 본문
[동시성 2편] 비관적 락(Pessimistic Lock)
안녕하세요!
이전 편에서는 "동시성 제어란?" 이란 내용으로 동시성이 무엇인지 알아보았습니다.
이제 본격적으로 첫 번째 실전 대책, 비관적 락(Pessimistic Lock)에 대해 알아보도록 하겠습니다.
이전 편 보러 가기
[동시성] 동시성 제어란? - 1편 (데이터가 꼬이지 않는 백엔드의 첫걸음)
1. 비관적 락이란?
- 말 그대로 "충돌이 난다고 미리 가정"하고, 데이터를 사용하는 동안 다른 트랜잭션의 접근 자체를 막는 방식입니다.
- 트랜잭션이 데이터를 읽거나 변경하는 동안 DB가 자동으로 행(row) 또는 테이블 전체에 락을 건다
→ 데이터 일관성 100% 보장(속도보다 ‘정확’ 우선)
→ 다른 트랜잭션은 내 작업이 끝날 때까지 ‘대기’ 또는 ‘타임아웃/예외’
예를 들어, 은행 계좌 이체, 재고 차감처럼 “누가 먼저 건드리나”에 따라 결과가 달라지면 곤란한 데이터에 필수입니다
2. 비관적 락이 필요한 예시
- 재고 감소:
→ 여러 사람이 동시에 마지막 상품을 주문할 때, 실제로 1명만 성공! - 적립금·포인트 차감, 계좌 이체:
→ 중복 차감, 이중 지급 같은 오류 완벽 차단
→ 정말 중요한 데이터(돈, 수량)는 ‘비관적 락’이 기본 방패막
3. JPA로 비관적 락 해보기
1. JPA의 @Lock 어노테이션 + PESSIMISTIC_WRITE
// 예제 엔티티
@Entity
public class Stock {
@Id @GeneratedValue
private Long id;
private Integer quantity;
// ... getter, setter 등
}
// Repository
public interface StockRepository extends JpaRepository<Stock, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select s from Stock s where s.id = :id")
Stock findByIdForUpdate(@Param("id") Long id);
}
2. 서비스에서 실제 락 걸기
@Service
public class StockService {
@Autowired
private StockRepository stockRepository;
// 트랜잭션 안에서 락 + 처리
@Transactional
public void decrease(Long stockId, int amount) {
Stock stock = stockRepository.findByIdForUpdate(stockId); // 락 걸림!
if (stock.getQuantity() < amount) throw new RuntimeException("재고 부족");
stock.setQuantity(stock.getQuantity() - amount);
// 트랜잭션 커밋 시 DB에서 락 해제
}
}
3. Native Query로 락 걸기
@Query(value = "select * from stock where id = :id for update", nativeQuery = true)
Stock nativeLock(@Param("id") Long id);
실제로는 SELECT ~~~ FOR UPDATE 쿼리가 나가며, 원하는 행에 락을 걸게 됩니다.
4. 락 사용 시 주의점
- 트랜잭션은 무조건 짧게!
→ 락 걸린 상태로 오래 유지하면 → 경합·데드락 위험 ⬆ - LockTimeoutException 대비
→ 경합이 심하면 DB가 자동으로 예외 던짐 → 타임아웃/롤백 처리 필요 - 낙관적 락보다 쓰기 작업 빈도가 높을 때, 충돌 가능성이 높을 때 ‘비관적 락’이 더 안전
- 읽기 작업(조회 위주)에는 가급적 피하기 ☝️
→ 동시성이 너무나 떨어지기 때문에 꼭 필요한 순간에만 걸 것!
5. 장/단점 요약
장점 | 단점 |
데이터 일관성 100% 보장 | 락 경합 많으면 성능 하락 가능성 |
정말 중요한 값은 항상 보호 | 트랜잭션 길어지면 데드락/지연 발생 |
구현이 너무 쉬움 (어노테이션 1줄) | 읽기 작업까지 걸면 서버 막힘 주의 |
실무에서는 "이중 지불", "재고 처리" 등과 같은 중복처리가 치명적인 서비스에서 많이 사용하며, 쿠폰 한정 수량 이벤트, 콘서트 예약과 같은 서비스에서 많이 사용되곤 합니다.
다음 편에서는 "낙관적 락(Optimistic Lock)"을 @Version, 버전 충돌 검증 방식과 함께 다뤄볼 예정입니다. 궁금한 점이 있다면 댓글로 남겨주세요!
'Tech > Spring' 카테고리의 다른 글
[동시성 제어 3편] 낙관적 락(Optimistic Lock) - @Version 어노테이션을 활용한 락 (3) | 2025.08.06 |
---|---|
[동시성 제어 1편] 동시성 제어란? - 데이터가 꼬이지 않는 백엔드의 첫걸음 (4) | 2025.07.31 |
[JPA] JPA로 게시글 CRUD 만들기 – 10분 만에 따라하기 (3) | 2025.07.29 |
[Spring] QueryDSL vs MyBatis vs JPA, 실무에서 언제 쓰는게 좋을까? (0) | 2025.04.29 |
[Spring] JPA 영속성 컨텍스트 (Persistence Context) 란? (0) | 2025.04.28 |
Comments