인프런

비관적 락 vs 낙관적 락 설명

recording or reCoding 2026. 5. 26. 14:54

핵심 개념

두 락 방식의 차이는 "충돌이 일어날 것이라고 가정하냐, 아니냐" 입니다.

비관적 락낙관적 락
가정 충돌이 자주 일어난다 충돌이 거의 없다
방식 DB 레벨에서 미리 잠금 버전으로 충돌 감지 후 재시도
단점 성능 저하 (대기) 충돌 시 재시도 비용

방식 1 — UPDATE로 처리하는 비관적 락

PessimisticLock1

가장 단순한 방식입니다. UPDATE like_count = like_count + 1 쿼리 자체가 원자적(atomic)으로 실행되기 때문에 DB가 행 레벨 잠금을 자동으로 처리합니다.

@Transactional
public void likePessimisticLock1(Long articleId, Long userId) {
    articleLikeRepository.save(
        ArticleLike.create(snowflake.nextId(), articleId, userId)
    );

    int result = articleLikeCountRepository.increase(articleId);
    if (result == 0) {
        // UPDATE된 행 없음 → 최초 요청이므로 INSERT
        articleLikeCountRepository.save(
            ArticleLikeCount.init(articleId, 1L)
        );
    }
}
1) increase() 내부에서 UPDATE ... SET like_count = like_count + 1 실행
2) DB가 해당 행에 Row Lock을 자동으로 설정 → 동시 요청이 순차 처리됨
3) 반환값이 0이면 행이 없었다는 뜻 → INSERT로 초기화

 

 

방식 2 — SELECT FOR UPDATE 비관적 락

PessimisticLock2

조회 시점에 명시적으로 잠금을 걸어 읽기부터 쓰기까지 전 과정을 보호합니다.

@Transactional
public void likePessimisticLock2(Long articleId, Long userId) {
    articleLikeRepository.save(...);

    ArticleLikeCount count =
        articleLikeCountRepository
            .findLockedByArticleId(articleId)  // SELECT ... FOR UPDATE
            .orElseGet(() -> ArticleLikeCount.init(articleId, 0L));

    count.increase();
    articleLikeCountRepository.save(count);
}
SELECT FOR UPDATE 실행
해당 행에 잠금을 획득 — 다른 트랜잭션은 이 지점에서 대기
엔티티 수정
잠금을 유지한 채로 increase() 호출
save() → 트랜잭션 커밋
커밋 후 잠금 해제 → 대기 중이던 다음 트랜잭션 진행

 

 

 

 

방식 3 — 낙관적 락

OptimisticLock

잠금 없이 조회 후, 저장 시점에 @Version 필드로 충돌을 감지합니다.

@Transactional
public void likeOptimisticLock(Long articleId, Long userId) {
    articleLikeRepository.save(...);

    ArticleLikeCount count =
        articleLikeCountRepository
            .findById(articleId)  // 일반 SELECT (잠금 없음)
            .orElseGet(() -> ArticleLikeCount.init(articleId, 0L));

    count.increase();
    articleLikeCountRepository.save(count);
    // 내부: UPDATE ... WHERE version = 기존버전
    // 버전 불일치 → OptimisticLockException!
}
트랜잭션 A — 조회 (version = 1)
잠금 없이 읽고 수정 진행
트랜잭션 A — 저장 성공 (version 1 → 2)
UPDATE ... WHERE version = 1 → 성공
트랜잭션 B — 저장 실패
version = 1을 기대했지만 이미 2 → OptimisticLockException → 재시도 필요

세 방식 한눈에 비교

방식잠금 시점쿼리주의사항

PessimisticLock1 UPDATE 실행 시 UPDATE (원자적) 최초 요청 동시성 이슈
PessimisticLock2 SELECT 시점 SELECT FOR UPDATE 대기 시간 증가 가능
OptimisticLock 커밋 시점 UPDATE WHERE version 재시도 로직 필요

 

'인프런' 카테고리의 다른 글

대용량 데이터에서 인덱스에 대한 고민.  (0) 2026.04.11