Backend/SpringBoot-JPA
Optimistic/Pessimistic Lock
Jayyy.H
2021. 4. 19. 22:49
LOCK
OPTIMISTIC LOCK
- 트랜잭션의 대부분은 충돌이 발생하지 않는다는 것을 가정하고 접근하는 방법으로 트랜잭션이 커밋하기 전까지는 충돌 여부를 알 수 없다.
- JPA에서 OPTIMISTIC LOCK을
@Version
을 사용하므로 Entity 정의 시@Version
이 명시된 필드를 추가하도록 하자. 또한,@Version
이 정의되어 있다면 OPTIMISTIC LOCK을 사용할 것을 명시하지 않더라도 DEFAULT 적용 된다. - 이러한 OPTIMISTIC LOCK은 COMMIT 시점에 충돌을 알 수 있으며 충돌 시 다음과 같은 Exception이 발생한다.
javax.persistence.OptimisticLockException
in JPAorg.hibernate.StaleObjectStateException
in Hibernateorg.springframework.orm.ObjectOptimisticLockingFailureException
in Spring
@Version
- OPTIMISTIC LOCK 사용 시
@Version
어노테이션을 이용해 버전관리 기능을 추가해야 한다. 이는Long
,Integer
,Short
,Timestamp
타입에 적용 가능하다.-
@Version private Long version;
-
- 엔티티를 수정할 때 마다 버전이 하나씩 자동으로 증가하며, 수정 시 조회 시점의 버전과 수정 시점의 버전이 다르면 예외를 발생시킨다. 이를 통해
second lost update problem
을 최초 커밋만 인정함으로써 방지할 수 있다. 다른 방법으로는 마지막 커밋만 인정, 충돌 내용 병합이 있으나 여기서 다룰 문제는 아니다.- tx-1> Member1 조회 (version 1)
- tx-2> Member1 조회 (version 1)
- tx-2> Member1 수정 (version 1->2)
- tx-1> Member1 수정 (충돌 발생)
- 엔티티 수정 시 다음과 같은 UPDATE 쿼리가 발생된다.
-
UPDATE MEMBER SET NAME=${NEW_NAME}, VERSION=${OLD_VERSION + 1} WHERE ID=${ID} AND VERSION=${OLD_VERSION}
- 이러한 버전관리 필드는 JPA가 직접 관리하는데, 벌크 연산시에는 개발자가 관리해 주어야 한다.
-
UPDATE MEMBER m SET m.name = ${NEW_NAME}, m.version = m.version + 1
-
PESSIMISTIC LOCK
- 트랜잭션의 충돌이 발생한다고 가정하고 우선 LOCK을 걸고 보는 방법이다.
SELECT FOR UPDATE
, `` 등의 방법이 있다.- 데이터를 수정하는 즉시 충돌여부를 확인할 수 있으며, 다음과 같은 Exception이 발생한다.
javax.persistence.PessimisticLockException
in JPAorg.springframework.dao.PessimisticLockingFailureException
in Spring
- PESSIMISTIC LOCK을 얻은 트랜잭션이 LOCK을 풀기 전까지 다른 트랜잭션들은 무한히 대기하게 되므로 다음과같이 timeout을 설정하는 것이 좋다.
-
Map<String, Object> properties = new HashMap<String, Object>(); // timeout for 10 seconds properties.put("javax.persistence.lock.timeout", 10 * 1000); Member member = em.find(Member.class, id, LockModeType.PESSIMISTIC_WRITE, properties);
-
PessimisticLockScope
를 통해 해당 Entity만 Lock을 걸지, 연관관계에 있는 Entity들도 Lock을 걸지 결정할 수 있다.PessimisticLockScope.NORMAL
: 해당 Entity만 Lock을 건다.PessimisticLockScope.EXTENDED
: 연관된 Entity들도 Lock이 걸린다.
JPA Lock
- JPA 사용 시,
READ COMMITTED
+OPTIMISTIC LOCK
을 조합하여 사용하는 것이 좋다. - Lock을 사용하는 대표적인 예는
- Entity Manager:
Member foundMember = em.find(Member.class, id, LockModeType.OPTIMISTIC);
,em.lock(foundMember, LockModeType.OPTIMISTIC);
,em.refresh()
- Query:
em.createQuery("select ~").setLockMode(LockModeTpye.OPTIMISTIC_INCREMENT);
-
가 있다.@NamedQuery(name="lock", query="SELECT ~~", lockMode = OPTIMISTIC_INCREMENT)
- Entity Manager:
LockModeType의 종류
Lock Mode | Type | 설명 |
---|---|---|
OPTIMISTIC LOCK | OPTIMISTIC |
- OPTIMISTIC LOCK을 사용한다. - @Version 만 적용했을때에는 Entity 수정 시에만 버전을 확인하지만, 여기에서는 조회만 하여도 버전을 체크함으로써 트랜잭션이 종료될 때 까지 다른 트랜잭션에서 변경하지 않음을 보장해준다. - 트랜잭션 커밋 시 버전정보를 조회( SELECT )해 Entity의 버전과 같은지 확인한다. - DIRTY READ , NON-REPEATABLE READ 를 방지한다. |
OPTIMISTIC LOCK | OPTIMISTIC_FORCE_INCREMENT |
- OPTIMISTIC LOCK과 함께 버전정보를 강제로 증가한다. - Entity를 수정하지 않아도 트랜잭션 커밋 시 버전정보를 강제로 증가시킨다. 이 때, 조회시점의 버전과 다르면 Exception을 발생시킨다. - Entity 수정 시 버전 UPDATE가 발생하는데, 트랜잭션 커밋 시점에도 버전정보가 증가하므로 2번의 버전 증가가 발생할 수 있다. - 연관관계가 있는 Entity들에 대해 연관관계의 주인인 Entity의 변경이 발생하면 연관관계에 있는 Entity의 버전정보도 동일하게 변경된다. ( Member 변경 시 Team 또한 Version이 같이 업데이트 ) |
PESSIMISTIC LOCK | PESSIMISTIC_READ |
- PESSIMISTIC LOCK, READ LOCK을 사용한다. - 데이터를 읽기만하고 수정하지 않는 용도로 사용한다. - shared lock 으로 select ... for share 와 같다. |
PESSIMISTIC LOCK | PESSIMISTIC_WRITE |
- PESSIMISTIC LOCK, WRITE LOCK을 사용한다. - Lock이 걸린 row는 다른 트랜잭션이 수정할 수 없다. - 내부적으로 select ... for update 를 사용해 lock을 건다. - exclusive lock |
PESSIMISTIC LOCK | PESSIMISTIC_FORCE_INCREMENT |
- PESSIMISTIC LOCK, 버전정보를 강제로 증가한다. - PESSIMISTIC LOCK 중 유일하게 버전정보를 사용한다. - for update nowait 을 사용한다. |
기타 | NONE |
- LOCK을 사용하지 않는다. - @Version 필드가 있으면 OPTIMISTIC LOCK이 적용된다. - Entity 수정 시, 버전 필드를 체크하면서 버전을 증가시킨다. |