락(Lock)은 데이터베이스에서 동시성 문제를 해결하기 위한 메커니즘으로, 여러 트랜잭션이 동시에 같은 데이터를 읽거나 쓰려고 할 때 충돌이나 데이터 일관성 문제를 방지하기 위해 사용됩니다. 즉, 데이터에 대한 접근을 제한하거나 순서를 제어함으로써 데이터의 정합성을 유지하는 역할을 합니다.

락의 주요 목적:

  • 데이터 일관성 보장: 여러 트랜잭션이 동시에 같은 데이터를 수정하려고 할 때, 데이터가 엉키거나 잘못된 상태가 되는 것을 방지합니다.
  • 동시성 제어: 다수의 사용자가 동시에 데이터를 읽거나 수정하는 경우, 트랜잭션 간의 충돌을 피하고 안전한 데이터 처리를 보장합니다.

락의 종류

  1. 공유 락 (Shared Lock, S Lock)

    • 읽기 전용 락으로, 데이터를 읽는 트랜잭션이 공유 락을 획득하면 다른 트랜잭션도 읽기는 가능하지만, 수정은 불가능합니다.
    • 여러 트랜잭션이 동시에 읽기는 가능하지만, 수정하려는 트랜잭션은 대기해야 합니다.
  2. 배타 락 (Exclusive Lock, X Lock)

    • 쓰기 전용 락으로, 트랜잭션이 데이터를 수정할 때 사용하는 락입니다. 배타 락을 획득한 트랜잭션이 있을 때는 다른 트랜잭션이 그 데이터를 읽거나 수정할 수 없습니다.
    • 한 트랜잭션이 배타 락을 가진 동안, 다른 트랜잭션은 해당 데이터에 대한 접근이 차단됩니다.

락의 동작 예시

1. 공유 락(S Lock) 예시

  • 트랜잭션 A공유 락을 획득하고 데이터를 읽습니다.
  • 동시에 트랜잭션 B도 같은 데이터에 공유 락을 획득하여 데이터를 읽을 수 있습니다.
  • 하지만 트랜잭션 C가 해당 데이터를 수정하려고 하면 배타 락을 요청해야 하고, 공유 락이 해제될 때까지 기다려야 합니다.

2. 배타 락(X Lock) 예시

  • 트랜잭션 A배타 락을 획득하고 데이터를 수정하고 있습니다.
  • 다른 트랜잭션은 해당 데이터를 읽거나 수정할 수 없으며, 트랜잭션 A가 작업을 완료하고 배타 락을 해제할 때까지 대기해야 합니다.

락과 격리 수준의 관계

락은 트랜잭션의 격리 수준에 따라 자동으로 적용되기도 합니다. 격리 수준이 높아질수록 데이터 충돌을 방지하기 위해 락이 더 많이 걸리며, 동시성 제어가 엄격해집니다.

  • READ UNCOMMITTED: 락을 거의 사용하지 않습니다. 다른 트랜잭션이 데이터를 수정 중일 때도 읽을 수 있습니다.
  • READ COMMITTED: 쓰기 작업에만 락을 사용하여, 다른 트랜잭션이 수정 중인 데이터를 읽지 않도록 합니다.
  • REPEATABLE READ: 트랜잭션 동안 읽기 락(S Lock)을 사용하여 데이터가 변경되지 않도록 보장합니다.
  • SERIALIZABLE: 가장 엄격한 락을 사용하여 트랜잭션 간 충돌을 완전히 방지합니다. 트랜잭션이 끝날 때까지 데이터를 읽거나 쓰지 못하게 막습니다.

락의 문제점

  1. 교착 상태 (Deadlock):

    • 두 개 이상의 트랜잭션이 서로 상대방이 해제하기를 기다리면서 무한정 대기하는 상태입니다. 이를 방지하려면 적절한 순서로 락을 획득하거나 타임아웃 설정이 필요합니다.
  2. 락 경합 (Lock Contention):

    • 여러 트랜잭션이 동시에 동일한 데이터에 접근하려고 할 때 발생하는 현상입니다. 락이 걸린 데이터에 대한 대기가 길어져 성능이 저하될 수 있습니다.
  3. 낮은 성능:

    • 락을 많이 사용하면 동시 처리 성능이 떨어질 수 있습니다. 특히 높은 격리 수준을 사용할수록 락이 자주 발생하므로 성능이 저하될 수 있습니다.

Spring에서 락 사용 예시

스프링에서는 @Transactional 어노테이션과 함께 데이터베이스 트랜잭션에서 락을 제어할 수 있습니다. 특히 Pessimistic Lock(비관적 락)Optimistic Lock(낙관적 락)의 두 가지 락 전략을 사용할 수 있습니다.

1. 비관적 락(Pessimistic Lock)

비관적 락은 데이터를 조회할 때 바로 락을 걸어서 다른 트랜잭션이 해당 데이터를 수정하지 못하도록 방지하는 방식입니다.

import org.springframework.data.jpa.repository.Lock;
import javax.persistence.LockModeType;

public interface PersonRepository extends JpaRepository<Person, Long> {

    @Lock(LockModeType.PESSIMISTIC_WRITE)  // 비관적 락 적용
    Optional<Person> findById(Long id);
}

2. 낙관적 락(Optimistic Lock)

낙관적 락은 데이터가 수정될 때만 충돌을 체크하는 방식입니다. 데이터 수정이 빈번하지 않은 상황에서 효율적으로 사용할 수 있습니다.

낙관적 락은 JPA의 @Version 어노테이션을 사용하여 구현합니다.

@Entity
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @Version  // 낙관적 락을 위한 버전 필드
    private Integer version;
}

낙관적 락은 버전 필드를 통해 수정 중에 다른 트랜잭션이 해당 데이터를 수정했는지 확인하며, 충돌이 발생하면 예외를 던집니다.


요약

  • 락(Lock)은 데이터의 일관성과 무결성을 유지하기 위해 트랜잭션 간의 데이터 접근을 제어하는 메커니즘입니다.
  • 공유 락은 읽기 작업에서 사용되고, 배타 락은 쓰기 작업에서 사용됩니다.
  • 락을 너무 많이 사용하면 성능이 저하될 수 있으며, 교착 상태 같은 문제가 발생할 수 있습니다.
  • 비관적 락낙관적 락은 트랜잭션 간 동시성 문제를 해결하는 주요 방법입니다.

+ Recent posts