비관적 락(pessimistic lock)
트랜잭션 동시성 제어 문제가 발생할 것이라고 가정하여 리소스에 대한 접근을 제어하기 위해 데이터베이스에서 락을 명시적으로 설정하는 방식이다
트랜잭션이 실행되는 동안 데이터에 읽기 락 또는 쓰기 락을 설정하여 다른 트랜잭션이 해당 데이터에 접근하거나 수정하지 못하도록 한다
락 획득 대기 시간을 지정하여 일정 시간이 지나면 실패로 처리할 수도 있다
비관적 락 종류
비관적 읽기 락
읽기 보호
- 데이터를 읽는 동안 다른 트랜잭션에서 해당 데이터를 수정할 수 없도록 방지하는 락이다
- 공유 락(Shared Lock)이라고도 하며 다수의 트랜잭션이 동시에 읽을 수 있지만 수정은 할 수 없다
사용
- 충돌 가능성이 낮으면서 데이터 무결성을 보장해야 되는 상황에서 사용한다
비관적 쓰기 락
읽기/쓰기 보호
- 데이터를 읽는 동안 다른 트랜잭션에서 읽거나 쓸 수 없도록 방지하는 락이다
- 데이터를 읽거나 쓰려는 시도가 모두 차단된다
- 배타 락(Exclusive Lock)이라고도 하며 하나의 트랜잭션만 데이터에 접근할 수 있다
사용
- 충돌 가능성이 높고 빈번하게 데이터가 변경되는 상황에서 사용된다
- 재고 관리 등
낙관적 락(optimistic lock)
트랜잭션 동시성 제어 문제가 발생하지 않을 것이라고 가정하고 데이터베이스 락을 설정하지 않는 대신 애플리케이션 차원에서 버전 관리를 통해 방지한다
애플리케이션에서 엔티티의 버전을 변경하면 데이터가 커밋될 때 변경된 버전을 비교하여 충돌을 감지한다
낙관적 락은 데이터베이스에 커밋되기 전까지 충돌 여부를 알 수 없고 버전 검증을 통해 충돌이 발생되면 그에 대한 후처리를 수행한다
두 개의 트랜잭션에서 동시에 엔티티를 조회한 상황을 가정해보자
각 트랜잭션에서 엔티티를 수정하면서 정의된 버전의 값이 변경될 것이다
같은 버전의 엔티티를 조회했기 때문에 변경된 버전의 값도 동일하다
-- 비즈니스 로직 수행 --
트랜잭션 A, B -> 엔티티 조회 (버전: 1)
트랜잭션 A, B -> 엔티티 수정
-> 버전 변경(트랜잭션 A: 1 -> 2, 트랜잭션 B: 1 -> 2)
트랜잭션 A에서 먼저 커밋하여 엔티티의 버전도 변경한다
이후 트랜잭션 B가 커밋하는데 트랜잭션 A에서 변경한 버전과 동일한 버전인 것을 감지하여 예외가 발생한다 (버전 충돌)
트랜잭션 A 커밋 -> 엔티티 버전 업데이트 (버전: 2)
트랜잭션 B 커밋 -> 트랜잭션 B의 엔티티 버전: 2
-> 트랜잭션 A에서 커밋된 엔티티와의 버전 비교 -> 버전 충돌
위의 방식이 기본적인 낙관적 락의 동작 방식이다
낙관적 락 종류
낙관적 읽기 락(optimistic read lock)
읽기 보호
- 트랜잭션에서 읽기 작업을 하는 도중 데이터가 수정되지 않도록 보호하는 락이다
- 읽기 작업에서 안정성을 보장한다
동작
- 데이터를 읽을 때 버전을 확인하고 읽기 이후 데이터가 변경되지 않았는지 검증한다
- 데이터를 수정하지 않아도 충돌 여부를 감지할 수 있다
- 데이터가 변경되지 않은 경우에만 정상 종료한다
사용
- 읽은 데이터로 후속처리를 하지만 데이터 수정은 하지 않을 때 사용한다
낙관적 쓰기 락(optimistic write lock)
읽기/쓰기 보호
- 데이터를 수정할 때 충돌을 감지하고, 변경이 없는 상태에서만 수정을 허용하는 락이다
- 읽기 + 수정 작업에서 안정성을 보장한다
동작
- 데이터를 읽을 때 버전을 확인하고 트랜잭션을 커밋하여 데이터를 수정하려고 할 때 버전 검증을 통해 충돌을 감지한다
사용
- 데이터를 읽은 뒤 해당 데이터를 수정해야 하는 경우 사용한다 (다른 트랜잭션이 변경한 경우 충돌 발생 -> 롤백/재시도)
낙관적 락 충돌 처리 방법
충돌 발생 시 두 가지 방식으로 처리할 수 있다
- 재시도: 데이터를 다시 읽고 작업 재시도
- 롤백: 트랜잭션 롤백