낙관적 락과 비관적 락은 여러 트랜잭션이 같은 데이터를 수정할 때 갱신 유실을 방지하기 위한 동시성 제어 방식입니다.
낙관적 락은 충돌이 자주 발생하지 않을 것이라고 가정하고, 데이터를 읽을 때 DB 레벨에서의 물리적인 락을 걸지 않는 것입니다. 대신 수정 시점에 버전이 읽었을 때와 같은지 확인합니다. JPA에서는 엔티티에 @Version 필드를 두며, UPDATE의 조건에 식별자와 버전을 함께 사용합니다. 영향을 받은 행이 없다면 다른 트랜잭션이 먼저 수정한 것이므로 예외를 발생시킵니다.
@Version
private Long version;충돌이 적고 읽기가 많은 환경에서는 동시성이 높지만, 충돌이 발생하면 사용자에게 알리거나 안전하게 재시도하는 로직이 필요합니다.
비관적 락은 충돌이 발생할 가능성이 높다고 보고 데이터를 읽는 시점부터 데이터베이스 잠금을 획득합니다. JPA의 PESSIMISTIC_WRITE는 일반적으로 SELECT ... FOR UPDATE 같은 잠금 조회로 변환되어 다른 트랜잭션의 수정을 대기시킵니다.
@Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<Stock> findById(Long id);충돌이 잦고 반드시 순서대로 처리해야 하는 재고 차감 등에 유용하지만, 잠금 대기와 타임아웃, 교착 상태가 발생할 수 있습니다. 따라서 충돌 빈도, 재시도 비용, 응답 시간 요구사항을 기준으로 선택해야 합니다.
JPA 낙관적 락의 버전 검사는 언제 이루어지나요?
영속 엔티티의 변경이 플러시될 때 현재 버전을 조건으로 UPDATE하고 버전을 증가시킵니다. 수정된 행이 0개라면 버전이 이미 바뀐 것으로 판단하여
OptimisticLockException계열의 예외가 발생합니다.
낙관적 락 충돌은 무조건 재시도하면 되나요?
아닙니다. 재고 차감처럼 최신 값을 다시 읽어 동일한 명령을 안전하게 반복할 수 있을 때만 제한된 횟수와 백오프를 두고 재시도해야 합니다. 사용자 입력을 덮어쓸 수 있는 수정 작업이라면 충돌 사실을 알리고 병합이나 재입력을 요청하는 편이 안전합니다.
비관적 락의 교착 상태를 줄이는 방법은 무엇인가요?
트랜잭션을 짧게 유지하고 여러 행이나 테이블을 항상 같은 순서로 잠그며, 잠금 조회 조건에 적절한 인덱스를 사용해야 합니다. 교착 상태는 완전히 제거하기 어려우므로 DB가 희생 트랜잭션을 롤백할 때 애플리케이션에서 재시도할 수 있어야 합니다.
