본문 바로가기
Backend/Spring

[JPA] 동시성 제어 - 낙관적 락(Optimistic Lock), 비관적 락(Pessimistic Lock)

by Jiyoung Oh 2025. 2. 28.

낙관적 락 예외..?

Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): 
[com.postdm.backend.domain.member.domain.entity.Member#1]

 

프로젝트에서 테스트 코드를 작성하던 중, ObjectOptimisticLockingFailureException (낙관적 락 예외)가 발생했다.

위에 오류 메시지를 보면 동시에 여러 개의 트랜잭션이 같은 엔터티(Member)를 변경하려고 할 때 낙관적 락 예외가 발생했다. 이는 JPA (Hibernate)에서 Spring Data JPA를 사용해서 엔터티의 버전이 변경되었거나, DB에서 해당 데이터를 찾지 못할 경우 발생한다고 한다.

여기서 언급된 동시성 제어와 낙관적 락이 무엇이며, 비관적 락과의 차이점은 무엇인지 알아보고자 한다.


동시성 제어 (Concurrency Control)

동시성 제어(Concurrency Control)는 다중 사용자가 동시에 같은 데이터에 접근할 때, 데이터의 일관성과 무결성을 유지하는 기술이다. 데이터베이스 시스템(DBMS)이나 멀티스레드 환경에서 데이터 충돌(Data Race), 정합성 문(Inconsistency), 교착 상태(Deadlock) 등을 방지하기 위해 필수적으로 사용된다. 

다수의 트랜잭션이 동시에 실행될 때, 데이터베이스 시스템은 여러 트랜잭션이 서로 영향을 주지 않도록 해야 한다. 이러한 동시성을 제대로 관리하지 않으면 더티 리드(Dirty Read), 비반복 읽기(Non-Repeatable Read), 팬텀 리드(Phantom Read) 와 같은 문제가 발생할 수 있다.

따라서 데이터베이스에서 동시에 여러 트랜잭션이 수행될 때, 데이터의 일관성을 유지하기 위해 동시성 제어(Concurrency Control) 가 필요하다. 이때 대표적인 방법이 낙관적 락(Optimistic Lock)비관적 락(Pessimistic Lock) 이다. 이 외에 DBMS에서는 동시성 문제를 해결하기 위해 트랜잭션 격리 수준(Isolation Level)을 제공하기도 한다.


낙관적 락 (Optimistic Lock)

낙관적 락은 데이터 충돌이 적을 것이라 가정하고, 트랜잭션이 끝날 때 데이터 충돌을 감지하여 해결하  방식이다. 낙관적 락은 아래와 같은 특징을 갖는다.

  • 데이터를 수정하기 전(읽을 때)에 별도의 잠금(락)을 걸지 않음
  • 트랜잭션이 종료될 때 데이터가 변경되지 않았는지 확인 후 충돌이 발생하면 롤백
  • 일반적으로 버전 번호(Versioning) (@Version 필드) 타임스탬프(Timestamp)를 활용하여 변경 여부를 확인
  • 읽기(Read) 연산이 많고, 충돌이 적은 경우에 유용 (예시: 사용자 요청 처리)

장단점

  • ✅ 성능이 우수하며, 별도의 락을 걸지 않아도 됨
  •   데이터를 읽는 동안 다른 트랜잭션이 해당 데이터를 변경할 수 있어 데드락(Deadlock) 발생 가능성이 낮음
  • ✅ 읽기 성능이 중요할 때 유리함 
  • ❌ 트랜잭션 종료 시 충돌이 감지되면 재시도 로직이 필요
  • ❌ 충돌이 많으면 성능이 떨어질 수 있음

동작 방식

  1. 데이터를 읽어온다.
  2. 데이터의 버전(Version) 번호 또는 타임스탬프를 함께 가져온다.
  3. 데이터를 수정한 후, 업데이트 시 기존 버전과 비교하여 충돌 여부를 확인한다.
  4. 충돌이 없다면 업데이트 수행, 충돌이 발생하면 롤백 후 재시도

구현 방법 예시: JPA @Version 활용

아래 코드에서 @Version 어노테이션이 붙은 필드는 낙관적 락을 적용하는 역할을 한다.
데이터가 업데이트될 때마다 version  증가시키고, 업데이트 시 기존 version 값과 비교하여 충돌이 발생하면 예외를 발생시킨다.

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Version
    private Integer version;

    private String name;
    private int stock;

    // Getter, Setter
}

비관적 락 (Pessimistic Lock)

비관적 락은 데이터 충돌이 자주 발생할 것이라 가정하고, 데이터에 미리 잠금(락)을 걸어 다른 트랜잭션이 접근하지 못하도록 막는 방식이다. 비관적 락은 아래와 같은 특징을 갖는다.

  • 데이터를 수정하기 전 (읽을 때)에 다른 트랜잭션이 해당 데이터에 접근하지 못하도록 잠금(락)
  • 따라서 잠금(락)을 해제하기 전까지 다른 트랜잭션은 해당 데이터를 수정할 수 없음
  • 동시 수정이 자주 발생하는 경우 유용
  • 락을 획득하는 과정에서 교착 상태(Deadlock) 가 발생할 가능성이 있음
  • SELECT ... FOR UPDATE 또는 JPA의 @Lock(LockModeType.PESSIMISTIC_WRITE) 활용
  • 데이터의 일관성 유지가 중요한 경우에 유용 (예시: 금융 시스템)

장단점

  • ✅ 데이터 정합성(일관성)보장 (충돌이 발생하지 않음)
  • ✅ 충돌이 많을 때 효과적
  • ❌ 데이터에 락을 걸면 해당 데이터에 다른 트랜젝션이 접근이 불가능
    • ❌ 동시성이 낮아지고 데드락(Deadlock) 위험이 있음
    • ❌ 성능 저하 가능성

동작 방식

  1. 데이터를 읽을 때 잠금(락)을 설정한다.
  2. 다른 트랜잭션이 해당 데이터에 접근하려 하면 대기 상태가 된다.
  3. 트랜잭션이 종료되면 잠금(락)을 해제한다.

구현 방법 예시: JPA Pessimistic Lock 적용

아래 예제에서 PESSIMISTIC_WRITE를 사용하면 다른 트랜잭션이 동시에 데이터를 수정할 수 없도록 쓰기 잠금(Write Lock) 을 설정한다.

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT p FROM Product p WHERE p.id = :id")
Product findByIdWithPessimisticLock(@Param("id") Long id);

낙관적 락 vs 비관적 락 

  • 낙관적 락 (Optimistic Lock)충돌 가능성이 낮은 환경(읽기 작업이 많고 변경이 적은 경우)에서 사용
  • 비관적 락 (Pessimistic Lock): 쓰기 및 동시 수정이 빈번한 경우 데이터 무결성을 보장하기 위해 사용
  낙관적 락 (Optimistic Lock) 비관적 락 (Pessimistic Lock)
잠금 방식 데이터를 수정할 때 충돌 감지 데이터를 읽을 때 미리 잠금
성능 충돌이 적을 경우 성능이 좋음 충돌이 많을 경우 성능이 좋음
활용 사례 읽기 연산이 많고 충돌이 적은 경우 쓰기 연산이 많고 충돌이 빈번한 경우
교착 상태 발생하지 않음 발생 가능성 있음

 

 


[참고자료]

https://f-lab.kr/insight/understanding-optimistic-and-pessimistic-locking

'Backend > Spring' 카테고리의 다른 글

Spring 개요  (0) 2025.03.28