Database Lock

조윤호·2023년 8월 30일
0

Database

목록 보기
1/2

데이터베이스에서 동시성 문제를 해결하는 방법을 알아보자!

개요

데이터베이스에서 값을 변경하는 일은 다음 세 가지 단계로 진행된다.
1. 데이터베이스에서 값을 읽어온다. (ray의 현재 잔액은 1000원)
2. 연산을 수행한다. (200원 입금 -> 총액은 1000 + 200 = 1200원)
3. 연산 결과를 저장한다 (1200원 을 데이터베이스에 입력)

만약 1~3 과정 중 중간에 데이터베이스의 값이 변경된다면(ex: 1000 -> 500원), 실제와 다른 값이 최종 결과로 저장된다. (내 실제 계좌는 700원이어야 하는데, 1200원이 된다??!? 없는 돈이 생긴다!)
따라서 1~3의 과정은 "하나의 단위"로써 보장되어야 한다. 데이터베이스에서는 이러한 작업을 보장하기 위해 Lock을 이용한다.

낙관적 락과 비관적 락

Optimistic Lock (낙관적 락)

낙관적 락은 일반적으로 층돌이 일어나지 않을 것이라고 보고 작업을 수행하는 것을 말한다.
우선 작업을 수행한 뒤, 작업에 문제가 없는지 확인 후 데이터베이스에 저장한다.
(멀티스레딩에서의 CAS(Compare And Set) 알고리즘과 유사하다)

특징

  • 낙관적 : 데이터 갱신 시 충돌이 발생하지 않을 것이라고 보는 것

  • 비선점적 : 우선적으로 락을 걸지 않고 진행

  • 어플리케이션 락을 통한 충돌 방지

  • 장점

    • 데드락 적음
    • 성능상 이점이 있다.
  • 단점

    • 충돌 발생 시 오버헤드가 발생한다.

Pessimistic Lock (비관적 락)

비관적 락은 층돌이 일어날 것을 미리 방지하기 위해 다른 사람들이 데이터에 접근하지 못하도록 막는다.

특징

  • 비관적 : 데이터 갱신 시 충돌이 일어날 것이라고 보고 미리 잠금
  • 선점적 : 조회 시부터 우선적으로 락
  • 장점
    • 데이터 무결성
    • 충돌 시 오버헤드가 감소한다.
  • 단점
    • 데드락 발생 위험이 있다.
    • 충돌 없을 시 오버헤드가 발생한다.

Java에서 락 구현하기

낙관적 락

@Version

@Entity
public class MyEntity {
  @Id 
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private String id;
  
  private String name;

  @Version
  private Integer version;
}
  • Entity에 @Version을 가진 필드가 있을 경우, 잘못된 업데이트를 자동으로 방지한다.
  • 쓰기 동작에는 락을 적용하지만, 읽기 락에서는 락을 적용하지 않는다.
  • 충돌이 발생하여 Commit 실패 시, OptimisticLockException 예외가 발생한다.

@Lock(LockModeType.xxx)

// 1. 어노테이션 이용하기
@Lock(LockModeType.OPTIMISTIC) 
Optional<MyEntity> findById(Long id);

// 2. entityManager 파라미터 이용하기
em.find(MyEntity.class, id, LockModeType.OPTIMISTIC); 
  • 읽기 시점에도 낙관적 락이 발생하도록 설정하고자 한다면 뒤 방법을 이용할 수 있다.
  • Dirty Read, Non Repeatable Read를 방지할 수 있다.
// 1. 어노테이션 이용하기
@Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT) 
Optional<MyEntity> findById(Long id);

// 2. entityManager 파라미터 이용하기
em.find(MyEntity.class, id, LockModeType.OPTIMISTIC_FORCE_INCREMENT); 

비관적 락

쓰기 락 (배타 락, @Lock(value = LockModeType.PESSIMISTIC_WRITE))

// 1. Repository에서 어노테이션
@Lock(LockModeType.PESSIMISTIC_WRITE) 
Optional<MyEntity> findById(Long id);

// 2. 트랜잭션에 붙이기
@Transactional
@Lock(value = LockModeType.PESSIMISTIC_WRITE)
public ResponseDto myServiceMethod(...)

이때 mysql에서는 다음과 같은 동작이 일어난다

SELECT * FROM my_entity WHERE id = 1 FOR UPDATE;

읽기 락 (공유 락, @Lock(value = LockModeType.PESSIMISTIC_READ))

// 1. Repository에서 어노테이션
@Lock(LockModeType.PESSIMISTIC_READ) 
Optional<MyEntity> findById(Long id);

// 2. 트랜잭션에 붙이기
@Transactional
@Lock(value = LockModeType.PESSIMISTIC_READ)
public ResponseDto myServiceMethod(...)

이때 mysql에서는 다음과 같은 동작이 일어난다

SELECT * FROM my_entity WHERE id = 1 FOR SHARE;

더 많은 내용이 궁금하다면?

소마 14기 CS스터디그룹 github

출처

https://hudi.blog/jpa-concurrency-control-optimistic-lock-and-pessimistic-lock/
https://velog.io/@msung99/JPA-%EB%82%99%EA%B4%80%EC%A0%81-%EB%9D%BDOptimistic-Lock-%EA%B3%BC-%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BDPessimistic-Lock-%EC%9D%84-%ED%86%B5%ED%95%9C-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0
https://velog.io/@msung99/JPA-%EC%9D%98-%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD%EC%97%90-%EB%8C%80%ED%95%9C-LockModeType-%EA%B3%B5%EC%9C%A0-%EB%9D%BD%EA%B3%BC-%EB%B0%B0%ED%83%80-%EB%9D%BD

profile
한걸음씩 성실히

0개의 댓글