Spring/JPA / / 2024. 4. 14. 00:50

Lock

글을 쓰게 된 계기는 프로젝트 팀원분과의 이야기 중이였습니다.

펀딩 도메인을 개발하는 도중 한 펀딩에 대해 동시에 결제를 한다면 경쟁 상태에서 동시성이 일어나지 않을까? 라는 의문에서 시작되었습니다.

이는 MySQL의 s-lock과 x-lock에 대해 알고 있다면 나온 답이였습니다.

 

결제가 이루어질 때, 해당 펀딩에 관한 정보를 SELECT 이후 UPDATE가 진행되는 SELECT - FOR UPDATE 쿼리는 해당 레코드에서 X-LCOK을 획득합니다. 

이때 X-LOCK은 쓰기락을 의미하며 베타적 락과 동일한 의미입니다. 여기서 X-LOCK을 걸면 다른 트랜잭션은 읽기 락을 얻지 못합니다.

이때 주의해야 할 점은 S-LCCK 입니다.

S-LOCK은 읽기락과 동일한 의미로 사용됩니다 S-LOCK은 읽는 동안 수정이 발생하지 않게 잠그는 역할을 하게됩니다.

이렇게 되면 의문점이 하나 생깁니다.

S-LOCK과 X-LOCK이 둘 다 락을 거는 형태라면 읽기든 쓰기든 LOCK만 걸면 되는 것 아니냐?

여기서 S-LOCK의 차이점은 호환이 가능하다는 점 입니다.

다음 예시를 들어보겠습니다.

  1. 1번 트랜잭션이 한 레코드를 읽기 위해 S-LOCK(읽기락)을 획득합니다.
  2. 2번 트랜잭션 또한 해당 레코드에 대한 S-LOCK 획득을 시도하면 S-LOCK 획득이 가능합니다.
    이는 S-LOCK은 여러 트랜잭션에서 동시에 획득할 수 있기 때문입니다.
  3. 다만 이때 UPDATE를 하기 위해 X-LOCK을 해당 레코드에 획득을 시도하면 대기상태에 빠집니다.
    S-LOCK을 가진 상태에서 X-LOCK은 획득이 불가능하기 때문입니다.
  4. 해당 레코드에 대한 S-LOCK이 전부 빠진 상태에서야 X-LOCK 획득이 가능합니다.
  5. 반대로 X-LOCK이 걸려 있는 레코드에 S-LOCK 획득을 시도한다면 S-LOCK 또한 대기상태에 빠집니다.

다만 X-LOCK이 걸린 상태에서 MySQL에서는 SELECT가 가능한데 이는 MySQL InnoDB에서는 S-LOCK을 걸지 않습니다.

→ 이는 명시적으로 선언하면 S-LOCK 획득이 가능하나, 기본적으로 SELECT에서 S-LOCK을 획득하지 않습니다.

 

다음은 JPA 에서 X-LOCK을 사용하기 위한 어노테이션인 @Lock에 관해 알아보겠습니다.

JPA 에서 SELECT - FOR UPDATE 를 사용하기 위해 X-LOCK을 획득하는 방법은 다음과 같습니다.

public interface TransactionTestRepository extends JpaRepository<TransactionTest, Long> {
  @Lock(LockModeType.PESSIMISTIC_FORCE_INCREMENT)
  @QueryHints({@QueryHint(name = "javax.persistence.lock.timeout", value = "1000")})
  TransactionTest findByIdx(Long idx);
}

 

@Lock’ 어노테이션 property를 조금 더 살펴 봅시다

  • ‘@Lock(LockModeType.PESSIMISTIC_READ)’
    • 해당 리소스에 공유 락을 걸게 됩니다. 다른 트랜잭션에서 읽기는 가능하지만 쓰기는 불가능해집니다.
  • ‘@Lock(LockModeType.PERSSIMISTIC_WRITE)’
    • 해당 리소스에 배타 락을 걸게 됩니다. 다른 트랜잭션에서는 읽기와 쓰기 모두 불가능해집니다.
  • ‘@Lock(LockModeType.PERSIMISTIC_FORCE_INCREMENT)’
    • PERSIMISTIC_WRITE와 유사하게 동작하지만 추가적으로 낙관적 락처럼 버저닝을 하게 됩니다.
    • 따라서 버전에 대한 컬럼이 추가적으로 필요합니다.

주의할 점은 ‘@Lock’ 어노테이션이 붙은 메서드 호출은 JPA의 Transaction 내부에서 동작해야 한다는 점 입니다.

JpaRepository 인터페이스를 사용하는 경우 entityManager.getTransaction().begin() 메서드를 사용할 수 없기 때문에 @Transaction 어노테이션을 사용합니다.

‘@Transaction’ 어노테이션의 영역 밖에서 ‘@Lock’ 어노테이션이 붙은 메서드를 호출한다면 아래와 같은 에러를 만나게 됩니다.

javax.persistence.TransactionRequiredException: no transaction is in progress

또 한가지는 사용하는 데이터베이스에 따라서 javax.persistence.lock.timeout과 같은 옵션이 작동하지 않습니다

→ MariaDB에서는 동작하지 않습니다


참조글

https://wildeveloperetrain.tistory.com/128

 

SELECT FOR UPDATE / JPA를 사용한 비관적 잠금

'동시성(Concurrency)' 웹 서비스에서는 다수의 사용자들이 데이터베이스에 동시에 접근하는 경우가 빈번하게 발생합니다. 때문에 데이터의 일관성에 대한 처리가 필요한데요. 이를 '동시성(Concurren

wildeveloperetrain.tistory.com

https://velog.io/@soongjamm/Select-쿼리는-S락이-아니다.-X락과-S락의-차이

 

Select 쿼리는 S락이 아니다. (X락과 S락의 차이)

이 글에서 사용한 dbms는 MySQL 8.x 버전이고 innoDB engine 기준입니다.저는 Real MySQL을 통해 DB를 공부하던 중 S-Lock 과 X-Lock에 대해 알게되었습니다. 그리고 SELECT - FOR UPDATE 쿼리는 해당 레코드의 X-loc

velog.io

 

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유