Spring Data JPA
JPA에서 Lock
stophyeon
2024. 4. 21. 17:55
728x90
Lock의 원리
Lock은 데이터베이스 접근에 재한을 두는 것입니다.
좀 더 원리적으로 설명해본다면 Lock을 사용하고 있는 사용자와 다른 사용자들 간의 접근을 제어해 충돌을 방지하는 것입니다. 예를 들어 설명해보겠습니다.
사용자 1,2가 있다고 했을 때 두 사용자가 동시에 한 데이터를 수정하려고 한다면 트랜잭션이 끝나지 않고, 다른 트랜잭션이 시작된다면 나중에 끝나는 트랜잭션의 update쿼리문의 실행 내용만이 실제 DB로 반영되기 때문에 먼저 끝난 트랜잭션의 내용이 없어지게 됩니다.
문제점은 알았으니 Lock을 사용해서 동시성 문제가 해결되는 원리를 알아보겠습니다.
제가 이해한 내용은 Lock은 트랜잭션이 보유하고 있는 권리라는 것입니다.
한 트랜잭션이 Lock을 보유하게 된다면 현재 트랜잭션이 작업하고 있는 Tuple에 다른 트랜잭션은 작업을 실행하지 못하게 되고, 대기를 하게 됩니다. Lock을 보유한 트랜잭션이 끝나면 다음 트랜잭션이 Lock을 보유하게 됩니다.
Lock의 종류
1. 낙관적 락
- 트랜잭션을 사용하지 않고 version이라는 column을 추가해서 해당 데이터를 수정할 때 같은 version 값으로 수정하려고 한다면 나중에 실행된 트랜잭션을 무시하는 방식으로 데이터의 정합성을 보장
- 트랜잭션을 사용하지 않기 때문에 성능이 좋지 않습니다.
2. 비관적 락
- 쿼리를 실행하기 전에 Lock을 걸어서 다른 트랜잭션의 접근을 막는 것입니다.
- 비관적 락을 사용하기 위해서는 트랜잭션을 선언해야합니다.
3. S Lock
- Share Lock(공유 락)은 한 트랜잭션이 읽기 작업을 실행하는 중에 다른 트랜잭션에서 쓰기 작업을 대기시킵니다.
- S Lock은 같은 S Lock을 가진 트랜잭션에 대해서는 읽기 작업의 접근을 할 수 있습니다.
4. X Lock
- Exclusive Lock(배타적 락)은 한 트랜잭션이 쓰기 작업을 실행하는 중에 다른 트랜잭션에서 읽기,쓰기 작업을 대기시킵니다
- X Lock은 다른 트랜잭션을 모두 대기 시킵니다.
- InnoDB에서 기본적으로 insert, update, delete 쿼리문을 실행하면 자동적으로 X Lock이 걸리게 됩니다.
낙관적 락 vs 비관적 락
낙관적 락과 비관적 락 중 어느 것을 사용할지에 대한 기준은 데이터의 충돌 가능성입니다.
데이터 중 udpate와 delete와 같은 작업이 많이 발생되는 데이터는 낙관적 락을 통해서 Lock을 거는 것이 적절하지 않습니다. 이유는 낙관적 락은 충돌이 발생했을 때 롤백을 하지 않고, 단순히 충돌한 쿼리문을 무시하기 때문에 개발자가 직접 롤백을 시켜줘야 합니다. 하지만 데이터의 충돌 가능성이 높은 데이터에 모두 롤백하는 코드를 작성하는 것은 비효율적이기 때문에 비관적 락을 사용하는 것이 적절합니다.
하지만 비관적 락은 낙관적 락보다 성능이 좋지 않기 때문에 충돌 가능성이 낮은 데이터에 대해서는 낙관적 락을 사용하는 것으로 성능을 높입니다.
JPA에서 Lock
JPA에서 Lock을 사용하는 방법으로는 해당 메서드에 @Lock 어노테이션을 선언하는 것입니다.
@Lock( LockModeType.OPTIMISTIC ) | 낙관적 락 |
@Lock( LockModeType.PESSIMISTIC_READ ) | S Lock |
@Lock( LockModeType.PESSIMISTIC_WRITE ) | X Lock |
@Lock( LockModeType. PESSINISTIC_FORCE_INCREMENT ) | 비관적 락 중 version을 사용 |
Lock에 대한 오해
S Lock은 select 쿼리에 선언하고, X Lock은 update와 delete 쿼리문에 선언하는 것이라고 오해하는 분들이 많다.
InnoDB에서 select를 제외한 쿼리문들은 기본적으로 X Lock이 자동으로 사용됩니다.
때문에 선언을 할 필요가 없습니다. 그렇다면 X Lock은 어디에 선언하는 것일까요
위의 @Lock 어노테이션들은 모두 select쿼리문에 선언합니다. 그렇다면 쓰기 작업에 Lock을 설정하지 않아도 기본으로 X Lock이 설정되므로 데이터 충돌이 일어나지 않는지 확인해보겠습니다.
test 결과 update된 데이터를 읽어오지 않았고, 쿼리문의 실행 순서를 보면 update문이 가장 나중에 실행된 것을 확인할 수 있습니다. update 쿼리문이 가장 늦게 실행된 이유는 Hibernate에서 설정된 쿼리문의 실행 순서 때문입니다.
Hibernate는 select -> delete -> insert -> update 순서로 쿼리문을 실행합니다.
그렇다면 select 쿼리에 X Lock을 설정한다면 결과가 어떻게 될까요
test 결과도 update된 데이터를 조회했고, 쿼리문의 실행 순서도 변경되었습니다.
단순히 select 쿼리문에 X Lock을 선언한 것으로 결과가 변경된 이유가 무엇일까요
X Lock을 선언한 select 쿼리는 select for update 쿼리로 변경됩니다.
select for update문은 update를 하기 위한 select 쿼리로 X Lock을 겁니다.
로그를 보면 X Lock을 걸지 않은 상태에서는 수정 쿼리가 나중에 끝나지만 X Lock을 적용한 상황에서는 조회 쿼리가 나중에 끝납니다. 그렇다는 것은 조회 트랜잭션이 쓰기 트랜잭션이 커밋되기 까지 대기했다는 것입니다.
select 문에 X Lock을 설정함으로써 다른 트랜잭션의 읽기 작업을 대기 시켰기 때문에 위와 같은 결과가 나온것입니다.