
데이터베이스에서 발생하는 충돌은 하나 이상의 트랜잭션이 특정한 데이터에 접근을 하여 동작을 할 때 발생한다.
그럼 어떤 충돌이 있는지 간단하게 확인을 하자.
(총 3개의 경우가 있는데 하나만 설명을 하면 나머지는 쉽게 이해를 할 수 있기 때문에 하나만 설명을 하겠다.)
위에서 충돌이 발생할 수 있는 경우에 대해서 쉽게 이해를 했다고 생각한다.
🤔그렇다면 어떤 방법으로 충돌을 예방할 수 있을까??
데이터베이스를 집, 데이터를 개인 귀중품이라고 가정을 하자.
모르는 누군가 내 집에 들어와서 귀중품을 도난하거나 접근 자체를 방지하기 위해서 보통 도어락을 사용한다.
이 처럼 데이터베이스에서 충돌을 방지하기 위해 락이라는 개념을 사용한다.
낙관적 락(Optimistic Lock)은 트랜잭션이 리소스에 접근할 때 별다른 락을 걸지 않고, 트랜잭션이 완료될 때 충돌 여부를 검사하는 방식이다.
일반적으로 version필드를 추가하여 데이터가 변경될 때 버전을 증가시키고, 트랜잭션이 커밋될 때 버전이 일치하는지 확인을 해야한다. 여기서 버전이 동일할 때 데이터를 업데이트가 가능하고, 버전이 다를 경우 해당 데이터 갱신이 불가능하다.

장점으로 락을 걸지 않으므로 동시성이 높고, 리소스 사용률이 적다. 그리고 충돌이 적은 환경에서는 성능이 뛰어나다.
하지만 단점으로는 충돌이 발생하면 트랜잭션을 재시도를 해야한다. 그렇기 때문에 충돌 빈도가 높으면 성능 저하가 발생한다는 부분이 있다.
@Entity
public class Entity {
@Id
private Long id;
@Version
private Integer version;
// ...
}
JPA에서는 엔티티 클래스에 @Version 어노테이션을 사용하여 낙관적 락을 적용 가능하다.
비관적 락은 트랜잭션이 리소스에 접근을 할 때 미리 락을 걸어 다른 트랜잭션이 해당 리소스에 접근하지 못하도록 하는 방식이다.
데이터베이스 수준에서 락을 걸어 다른 트랜잭션이 접근하려고 할 때 해당 트랜잭션을 대기하게 한다.

장점으로는 충돌이 발생할 가능성을 원천적으로 차단하여 데이터 무결성을 보장한다는 부분이 있다.
단점은 락을 걸고 해제하는 오버헤드가 발생하며 성능 저하가 발생할 수 있다. 그리고 동시성이 낮아질 수 있다는 점이 있다.
@Lock 어노테이션을 사용하거나, EntityManager를 사용하여 비관적 락 옵션을 설정 가능하다.@Lock 어노테이션을 사용@Lock어노테이션을 사용하여 특정 쿼리나 메서드에 비관적 락을 설정할 수 있다.@Entity
public class MyEntity {
@Id
private Long id;
// ...
}
public interface MyEntityRepository extends JpaRepository<MyEntity, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
// @Lock(LockModeType.PESSIMISTIC_READ)
@Query("SELECT e FROM MyEntity e WHERE e.id = :id")
MyEntity findByIdWithPessimisticWriteLock(@Param("id") Long id);
}
EntityManager를 사용하여 트랜잭션 내에서 비관적 락 옵션을 설정할 수 있다.entityManager.lock(entity, LockModeType.PESSIMISTIC_WRITE);
// 다른 트랜잭션에서 읽기만 가능
entityManager.lock(entity, LockModeType.PESSIMISTIC_READ);
// 다른 트랜잭션에서 읽기, 쓰기 불가능