트랜잭션, 락
트랜잭션, 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가 동시 수정하면 마지막 수정 사항만 남는다.
- 마지막만 인정
- 최초만 인정
- 둘의 내용 병합
@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.OptimisticLOckExceptionorg.hibernate.StaleObjectStateExceptionorg.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이 발생한다.