트랜잭션, 락

트랜잭션, ACID

  • Atomicity: 트랜잭션 내 작업은 하나의 작업처럼 모두 성공하거나 실패해야만 한다.
  • Contitnency: 모든 트랜잭션은 일관성있는 DB 상태를 유지해야 한다.
  • Isolation: 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않아야 한다.
  • Durability: 트랜잭션을 성공적으로 끝내면 항상 결과가 기록되어야 한다.

격리 정도

  • READ UNCOMMITED ( 커밋되지 않은 읽기)
  • READ COMMITTED(커밋된 읽기)
  • REPEATABLE READ ( 반복 가능한 읽기 )
  • SERIALIZABLE( 직렬화 가능 )
격리 수준 DirtyRead(ReadUncommitted) Non-RepeatableRead PhantomRead
READ UNCOMMITED o o o
READ COMMITTED   o o
REPEATABLE READ     o
SERIALIZABLE      
  • ReadUnCommitted: 커밋하지 않은 데이터를 읽을 수 있다.
  • ReadCommitted: 커밋한 데이터만 읽을 수 있다. 그러나 Non-RepeatableRead가 발생할 수 있다.
  • RepeatableRead: 한 번 조회한 데이터를 반복해서 조회해도 같은 데이터가 조회된다. PhatomRead가 발생할 수 있다.
  • Serializable: 가장 엄격한 격리 수준

참고 MVCC

낙관적 락 , 비관적 락

  • ReadCommitted라도 1차 캐시로 낙관적 락을 사용할 수 있다. 그러나 스칼라 값을 조회해서 영속성 컨텍스트 관리를 받지 못하면 크게 의미가 없다.

낙관적 락

  • 트랜잭션 대부분은 충돌하지 않는다고 낙관적으로 가정하는 방법이다.
  • DB락을 이용하는게 아니라 JPA에서 버전관리 기능을 사용한다.
  • 커밋하기 전까지는 충돌이 생길지 모른다.

비관적 락

  • 트랜잭션이 충돌한다고 가정하고 우선 락을 걸고 보는 방법이다.
  • selectForUpdate가 있다.

두 번 갱신 분실

  • A, B가 동시 수정하면 마지막 수정 사항만 남는다.
    1. 마지막만 인정
    2. 최초만 인정
    3. 둘의 내용 병합

@Version

  • 적용 가능한 타입은 Long, Integer, Short, Timestamp다.
  • 조회 시점 버전과 수정 시점의 버전이 다르면 예외를 발생시킨다.
  • 자연스럽게 최초 커밋만 인정하는 방향으로 결정된다.
  • 버전은 엔티티 값을 변경하면 증가한다.
  • @Version 값은 JPA가 직접 관리하므로 임의 수정하면 안된다.

JPA 락

  • 아래의 경우에 사용할 수 있다.
    • EntityManager.lock(), EntityManger.find (), EntityManager.refresh ()
    • Query.setLockMode()
    • @NamedQuery
  • Board board = em. find (Board.class, id, LockModeType.OPTIMISTIC) 같이 걸거나
  • em.lock(~, LockModeType.OPTIMISTIC) 이렇게 후에 걸 수 도 있다.

LockModeType

|락 |모드 타입| 설명| |:—-:|:—–|:—-:| |낙관적 락| OPTIMISTIC |낙관적 락을 사용한다.| |낙관적 락| OPTIMISTIC_FORCE_INCREMENT| 낙관적 락 + 버전정보를 강제로 증가한다.| |비관적 락| PESSIMISTIC_READ| 비관적 락 , 읽기 락을 사용한다.| |비관적 락| PESSIMISTIC_WRITE| 비관적 락 , 쓰기 락을 사용한다.| |비관적 락| PESSIMISTIC_FORCE_INCREMENT| 비관적 락 + 버전정보를 강제로 증가한다.| |기타 |NONE| 락을 걸지 않는다.| |기타 |READ |JPA1.0 호환 기능이다. OPTIMISTIC 과 같으므로 OPTIMISTIC 을 사용하면 된다.| |기타 |WRITE |JPA1.0 호환 기능이다. OPTIMISTIC_FORCE_IMCREMENT 와 같다.|

JPA 낙관적 락

  • @Version을 사용한다.
  • Exception은 아래와 같다.
    • jakarta.persistence.OptimisticLOckException
    • org.hibernate.StaleObjectStateException
    • org.springframework.orm.ObjectOptimisticLockingFailureException

None

  • 락옵션 없어도 @Version으로 적용한다.
  • 조회한 엔티티를 수정할 때 다른 트랜잭션에 의해 변경(삭제)되지 않아야 할 때 사용한다. (두 번 갱신 분실 문제 예방)

OPTIMISTIC

  • 엔티티 조회만 해도 버전을 체크한다.
  • 조회한 엔티티는 트랜잭션이 끝날 때까지 다른 트랜잭션에 의해 변경되지 않아야 한다. (조회 ~ 트랜잭션 끝까지 다른 트랜잭션에 변경되지 않아야 한다. -> DirtyRead, Non-RepeatableRead 방지)

OPTIMISTIC_FORCE_INCREMENT

  • 낙관적 락을 사용하면 버전 정보 강제로 증가.
  • 논리적인 단위의 엔티티 묶을음 관리할 수 있다. (엔티티 수정이 없어도 버전을 올린다. )

JPA 비관적 락

  • select for update 구문을 사용하면서 버전 정보는 저장하지 않는다.
  • 주로 PESSIMISTIC_WRITE 를 사용한다.
    • 엔티티가 아닌 스칼라로 조회할 때도 사용 가능
    • 데이터를 수정하는 즉시 트랜잭션 충돌을 감지할 수 있다.
  • 예외는 아래와 같다.
    • jakarta.persistence.PessimisticLockException
    • org.springframework.dao.PessimisticLockingFailureException

PESSIMISTIC_WRITE

  • 비관적 락이라고 할 경우 보통 이 옵션이다. DB 쓰기 락을 건다.
  • select for update로 락을 건다. -> Non-RepeatableRead 방지한다.

PESSIMISTIC_READ

  • 데이터 반복 읽기, 수정하지 않는 용도의 락이다.
  • vendor에 따라 PESSIMISTIC_WRITE로 걸리는 경우도 있다.

PESSIMISTIC_FORCE_INCREMENT

  • 비관적 락 중 유일하게 버전 정볼르 사용한다.
  • 비관적 락이지만 버전 정보를 강제로 증가시킨다.
  • nowait을 지원하지 않으면 for update로 동작한다.

참고 읽기락, 쓰기락

비관적 락, 타임 아웃

  • 무한정 기다릴 수 없을 때 타임아웃을 줄 수 있다. jakarta.persistence.LockTimeoutException이 발생한다.