Published on
•
👁️

락⭐

Authors
  • avatar
    Name
    River
    Twitter
11공유 락과 배타 락에 대해서 설명해주세요.쉬움
공유 락과 배타 락은 데이터베이스에서 동시성 제어를 위해 사용되는 두 가지 주요 락 메커니즘입니다. 공유 락과 배타 락은 접근 권한에 따른 분류로 공유 락은 select와 같은 읽기 전용 작업에 사용된다. 공유 락은 여러 트랜잭션이 같은 데이터에 동시에 공유 락을 획득하여 조회할 수 있지만, 공유 락이 설정된 데이터에 대하여 배타 락을 획득할 수 없다. 반면 배타 락은 insert, update, delete와 같은 쓰기 전용 작업에 사용된다. 다만 같은 데이터에 대하여 동시에 배타 락을 획득할 수 없으며, DBMS에서 쓰기 작업 시 자동으로 배타 락을 획득한다.
상세 설명

동시성 제어 매커니즘

  • Locking (락 기반 제어, 락 메커니즘)
  • Time-stamping (타임스탬프)
  • Optimistic Concurrency Control (낙관적 제어, OCC)

락 메커니즘

  • 다중 사용자 환경에서 데이터의 일관성과 무결성을 보호하기 위해 사용되는 동시성 제어 메커니즘
  • 여러 프로세스나 스레드가 공유 자원에 접근할 때 발생할 수 있는 충돌을 방지하기 위해 사용

주요 락 메커니즘

  • 접근 권한에 따른 분류
    • 공유 락 (S-Lock)과 배타 락 (X-Lock)
  • 적용 범위(Granularity)에 따른 분류
    • 데이터베이스 락, 테이블 락, 페이지 락
    • 레코드 락, 갭 락, 넥스트키 락
  • 구현 방식에 따른 분류
    • 비관적 락 (Pessimistic Locking)과 낙관적 락 (Optimistic Locking)
  • 특수 락 메커니즘
    • 2단계 락킹 (Two-Phase Locking)
    • MVCC (Multi-Version Concurrency Control)
    • 인텐션 락 (Intention Lock)
    • 타임스탬프 기반 동시성 제어
    • 분산 락 (Distributed Lock)

공유 락 (Shared Lock, S-Lock)

  • 여러 트랜잭션이 동시에 데이터를 읽을 수 있도록 허용하지만, 데이터를 수정할 수는 없다

  • 읽기 전용 작업에 사용되며, SELECT 쿼리 실행 시 획득

  • 여러 트랜잭션이 동시에 같은 데이터에 공유 락을 획득할 수 있다.

    공유 락이 설정된 데이터에 대해서는 다른 트랜잭션이 배타 락을 획득할 수 없다.

  • MySQL에서는 SELECT ... LOCK IN SHARE MODE로 명시적으로 공유 락을 설정할 수 있다.

  • 전통적인 DBMS는 SELECT 문에 공유 락을 자동으로 사용하지만, 오늘날 DBMS는 락을 사용하지 않고 MVCC를 사용하여 읽기 작업이 쓰기 작업을 차단하지 않도록 설계되어 있다.

    ⇒ 오늘날 DBMS는 select 쿼리에 공유 락을 쓰지 않고 MVCC를 사용한다.

배타 락 (Excluesive Lock, X-Lock)

  • 특정 트랜잭션이 데이터를 수정할 수 있지만, 이 동안 다른 트랜잭션은 해당 데이터에 접근할 수 없다
  • 데이터 변경 작업(INSERT, UPDATE, DELETE)에 사용
  • 배타 락이 설정된 데이터에는 다른 트랜잭션이 어떠한 락(공유/배타)도 획득할 수 없다.
  • MySQL에서는 SELECT ... FOR UPDATE로 명시적으로 배타 락을 설정할 수 있다.
  • DBMS에서 쓰기 작업은 기본적으로 배타 락을 자동으로 설정한다.

사용 예시

  • 은행 시스템에서 계좌 잔액을 조회할 때는 공유 락을 사용하여 여러 사용자가 동시에 조회할 수 있게 하지만, 잔액을 수정할 때는 배타 락을 사용하여 데이터의 일관성을 보장한다.

락 호환성

기존 락 / 요청 락공유 락(S)배타 락(X)
공유 락(S)가능불가능
배타 락(X)불가능불가능
  • 즉, 여러 트랜잭션이 동시에 읽기만 가능하다.
  • 하지만, 읽고 읽는 중이거나 쓰고 있는 중에 다른 트랜잭션이 쓰기 작업을 할 수 없다.

락 에스컬레이션 (Lock Escalation)

  • 레코드 락과 같은 세밀한 락(row-level)에서 테이블 락처럼 더 넓은 범위의 락(table-level)으로 전환하는 과정

  • 작은 범위의 세밀한 락이 많아지면 각각의 락을 유지, 관리하는 데 메모리와 CPU 리소스를 많이 사용한다.

    • 다만, 테이블 락이 설정되면 해당 테이블의 모든 행이 잠기게 된다.

      ⇒ 많은 트랜잭션이 하나의 락을 기다리게 되어 전체 시스템 처리량이 감소한다.

  • 락 에스컬레이션은 일반적으로 DBMS가 자동으로 수행한다.

    • MySQL은 기본적으로 행 수준 락과 넥스트키 락을 사용하며, 락 에스컬레이션 보다는 인덱스를 통한 최적화를 활용한다.
    • PostgreSQL 또한 행 수준 락을 사용하며 MVCC를 통해 락 경합을 최소화한다.
  • 락 에스컬레이션의 trade-off는 락 에스컬레이션이 시스템 리소스를 절약하기 위해 사용되지만, 동시성을 저하시킬 수 있다는 점이다.

12동시성 문제 중 경쟁 상태를 해결하려면 무엇이 보장되어야 하나요?보통
경쟁 상태는 두 개 이상의 프로세스나 스레드가 동시에 공유 자원에 접근할 때 발생하며, 실행 순서에 따라 결과가 달라지는 비결정적인 상황을 말합니다. 이를 해결하려면 상호 배제가 필요하고, 이는 한 번에 하나의 스레드만 자원에 접근하도록 보장하는 것입니다. 이러한 상호 배제를 구현하는 방법으로는 락, 세마포어, 모니터와 같은 동기화 메커니즘을 사용할 수 있습니다. Java의 경우 synchronized 키워드를 사용하거나 ReentrantLock, AtomicInteger 클래스를 사용하면 세밀하게 제어도 가능합니다. 데이터베이스에서는 트랜잭션의 격리 수준이나 락 메커니즘을 통해 상호 배제를 구현할 수 있습니다.
상세 설명

경쟁 상태 (race condition)

  • 두 개 이상의 프로세스나 스레드가 동시에 공유 자원에 접근할 때 발생할 수 있는 문제로

    실행 순서에 따라 결과가 달라지는 비결정적 상황을 의미한다.

    int counter = 0;
    
    void thread1() { counter += 1; }
    
    void thread2() { counter += 1; }
    
    • 스레드가 동시에 실행되었을 때 경쟁 상태가 발생하면 counter = 1이 나올 수 있다.
  • 공유 자원에 대한 읽기-수정-쓰기(read-modify-write) 연산이 원자적으로 수행되지 않을 때 주로 발생

  • 이를 해결하기 위해서는 주로 '상호 배제'가 보장되어야 한다.

  • 공유 자원은 여러 프로세스나 스레드가 동시에 접근할 수 있는 데이터나 장치이다.

    • 데이터베이스의 테이블, 레코드, 메모리의 특정 변수, 파일 등

상호 배제 (mutual exclusion)

  • 한 번에 하나의 프로세스나 스레드만 공유 자원에 접근할 수 있도록 하는 것
  • 상호 배제 구현 방법
    • 락 (Lock)

      lock.lock();     // 락 걸기 (자원 사용 시작)
      try {
          // 자원 사용
      } finally {
          lock.unlock(); // 락 해제
      }
      
      • 가장 기본적인 상호 배제 메커니즘
      • 임계 영역에 진입하기 전에 락을 획득하고, 작업이 끝난 후 해제하여 하나의 자원에 대한 동시 접근을 막는다.
    • 세마포어 (Semaphore)

      Semaphore sem = new Semaphore(3);
      sem.acquire();   // 자원 사용 전 - 카운터 감소
      // 자원 사용
      sem.release();   // 자원 사용 후 - 카운터 증가
      
      • 정수 값을 기반으로 여러 스레드의 접근을 제어하며, 지정된 수만큼 동시 접근을 허용할 수 있다.
      • 바이너리 세마포어(값이 0 또는 1인 세마포어)는 락과 유사하게 동작한다.
    • 모니터 (Monitor)

      synchronized(공유자원) {
          // 해당 자원을 사용하는 코드
      }
      
      • 언어 수준에서 제공하는 동기화 구조
      • Lock과 조건 변수를 함께 포함한 고수준 동기화 구조
      • Java의 synchronized 블록이 이에 해당

원자성(Atomicity) 보장 방법

  • 락 없이도 동시성 문제를 해결할 수 있는 대안적 기법
  • 원자적 연산 (Atomic Operations)
    • CPU 수준에서 지원하는 원자적 명령어(CAS, Compare-And-Swap 등)를 사용하여 락 없이도 안전한 연산이 가능하다.
  • 트랜잭션 메모리 (Transactional Memory)
    • 메모리 접근을 트랜잭션으로 관리하여 원자성을 보장한다.
  • AtomicInteger 등의 사용은 Lock-Free 프로그래밍의 예시로, 성능 병목을 줄이는 데 유리하다

데드락(Deadlock) 방지

  • 데드락이란, 여러 개의 프로세스 또는 스레드가 서로가 가진 자원을 기다리면서 영원히 작업을 끝내지 못하는 상태이다.
    • Thread 1 : 자원 1 보유 ⇒ 자원 2 기다리는 중
    • Thread 2 : 자원 2 보유 ⇒ 자원 1 기다리는 중
    • 이 상태로 영원히 멈추는 것을 데드락(교착 상태)이라고 한다.
  • 경쟁 상태를 해결하기 위해 락을 사용할 때는 데드락 발생 가능성을 고려해야 한다.
  • 데드락이 발생하는 조건은 아래 4가지 조건이 모두 만족할 때 발생한다.
    • 상호 배제 : 자원은 한 번에 하나의 프로세스(스레드)만 사용 가능
    • 점유 대기 : 자원을 가진 채로 다른 자원을 기다린다.
    • 비선점 : 자원을 강제로 뺏을 수 없음
    • 환형 대기 : 서로가 서로의 자원을 기다림
  • 락 획득 순서를 일관되게 유지하거나, 타임아웃을 설정하여 데드락을 방지할 수 있다.

언어 및 프레임워크 수준의 지원

  • Java
    • synchronized, volatile, java.util.concurrent 패키지(ReentrantLock, AtomicInteger 등)
    • Java에서는 'synchronized' 키워드를 사용하여 메서드나 블록을 동기화할 수 있다.
    • 또한, 'ReentrantLock' 클래스를 사용하여 보다 세밀한 제어를 할 수도 있다.
  • C++
    • std::mutex, std::atomic, std::lock_guard
  • 데이터베이스
    • 트랜잭션 격리 수준, 락(SELECT FOR UPDATE 등)

분산 시스템에서의 경쟁 상태 해결

  • 경쟁 상태는 MSA 환경에서도 발생한다.
  • 분산 락 (Distributed Lock)
    • Redis, ZooKeeper 등을 사용하여 여러 서버 간 상호 배제를 구현하는 것
  • 최종 일관성 (Eventual Consistency)
    • 분산 환경에서 일시적인 데이터 불일치를 허용하되, 시간이 지나면 결국 일관된 상태로 수렴하도록 보장하는 일관성 모델
  • CRDT (Conflict-free Replicated Data Type)
    • 충돌 없이 분산 데이터를 병합할 수 있는 자료구조를 사용하는 것

  • 경쟁 상태를 해결하려면 단순히 락만 사용하는 것에 그치지 않고, 원자적 연산, 언어 및 프레임워크 수준의 동기화 메커니즘, 그리고 분산 환경에서의 일관성 유지 전략까지 폭넓게 고려해야 한다.
13데이터베이스 시스템에서 동시성을 제어하는 방법에 대해 설명해주세요.보통
데이터베이스 시스템에서 동시성 제어 메커니즘은 여러 트랜잭션이 동시에 데이터베이스에 접근할 때 데이터 일관성과 무결성을 보장하기 위한 메커니즘입니다. 크게 3가지 방식이 존재하는데, Locking, Time-stamping, 낙관적 동시성 제어(OCC)가 있습니다. Locking 방식은 트랜잭션이 데이터에 접근할 때 잠금을 설정하여 다른 트랜잭션의 접근을 차단하는 가장 일반적인 방법이고, Time-stamping 방식은 각 트랜잭션에 타임스탬프를 부여하여 트랜잭션의 순서를 결정하고 이를 통해 충돌 여부를 판단하여 일관성을 유지합니다. 마지막으로 낙관적 동시성 제어인 OCC는 트랜잭션이 데이터에 접근할 때 잠금을 사용하지 않고, 커밋 시점에 충돌을 검사하여 문제가 있을 경우 롤백하는 방식으로 읽기 위주의 트랜잭션 간 충돌이 거의 없는 시스템에서 성능상 유리합니다.
상세 설명

데이터베이스 시스템에서 동시성 제어란?

  • 데이터베이스 시스템에서 동시성 제어는 여러 트랜잭션이 동시에 데이터베이스에 접근할 때 데이터의 일관성과 무결성을 보장하기 위한 메커니즘이다.
  • 동시성 제어 메커니즘은 크게 3가지 방식이 존재한다.
    • 잠금 (Locking)
    • 타임스탬프 (Time-stamping)
    • 낙관적 동시성 제어(Optimistic Concurrency Control, OCC)
  • 실무에서는 DBMS이 이러한 동시성 제어 메커니즘을 자동으로 처리하지만, 개발자는 트랜잭션 격리 수준을 설정하여 동시성 제어의 강도를 조절할 수 있다.

락 메커니즘 (Locking)

  • 가장 일반적인 방법으로, 트랜잭션이 데이터에 접근할 때 잠금을 설정하여 다른 트랜잭션이 해당 데이터에 접근하지 못하게 하는 것
  • 잠금(락 메커니즘)은 공유 락(읽기)과 배타 락(쓰기)으로 나뉜다.

타임스탬프 (Time-stamping)

  • 각 트랜잭션에 타임스탬프를 부여하여 트랜잭션의 순서를 결정한다.
  • 트랜잭션 실행 순서를 미리 결정하고, 충돌 여부를 타임스탬프 기반으로 판단한다.
  • 트랜잭션이 데이터에 접근할 때 타임스탬프를 비교하여 일관성을 유지한다.

낙관적 동시성 제어 (Optimistic Concurrency Control, OCC)

  • 트랜잭션이 데이터에 접근할 때 잠금을 사용하지 않고, 커밋 시점에 충돌을 검사하여 문제가 있을 경우 롤백한다.
  • 트랜잭션 간 충돌이 거의 없는 시스템(예: 읽기 위주 시스템)에서 성능상 유리

특수 락 메커니즘

  • 2단계 락킹 (Two-Phase Locking)
  • MVCC (Multi-Version Concurrency Control)
  • 인텐션 락 (Intention Lock)
  • 타임스탬프 기반 동시성 제어
  • 분산 락 (Distributed Lock)

2단계 락킹(2PL, Two-Phase Locking)

  • 트랜잭션은 확장 단계와 수축 단계의 2단계를 거친다.
  • 확장 단계에서는 락을 획득만 하고 해제하지 않으며, 수축 단계에서는 락을 해제만 하고 획득하지 않는다.
  • 이는 직렬화 가능성(serializability)을 보장한다.

MVCC (Multi-Version Concurrency Control)

  • 데이터의 각 버전을 트랜잭션마다 별도로 관리함으로써 서로 다른 버전을 볼 수 있어 읽기 트랜잭션이 쓰기 트랜잭션을 차단하지 않는다.

  • PostgreSQL, Oracle, MySQL의 InnoDB에서 사용된다.

    (MySQL의 InnoDB는 MySQL에서 사용하는 저장 엔진 중 가장 널리 쓰이는 기본 엔진)

인텐션 락 (Intention Lock)

  • 인덱스나 테이블처럼 계층적인 자원에 락을 적용할 때 상위 레벨에 락 충돌 없이 하위 레벨 락이 가능하도록 돕는 메타 정보용 락
  • Ex. 특정 레코드에 배타 락을 걸려면 그 테이블에 먼저 Intention Exclusive Lock(IX Lock)을 요청해야 한다.
  • 주로 InnoDB에서 사용되며, 락 충돌 여부를 빠르게 판단할 수 있도록 도와준다.

분산 락 (Distributed Lock)

  • 여러 노드/서버에서 공유 자원에 동시에 접근하지 못하도록 제어하는 락
  • 단일 메모리 공간이 아닌 분산 환경에서의 동기화 문제를 해결한다.
  • 보통 Redis, ZooKeeper, etcd 등을 활용해서 구현

분산 데이터베이스 동시성 제어

  • 2단계 커밋(2PC), 3단계 커밋(3PC) 프로토콜을 사용하여 여러 노드에 걸친 트랜잭션의 일관성과 원장성을 보장하기 위한 메커니즘
  • 트랜잭션 전체 흐름을 조율하는 프로토콜
    • 2PC : Coordinator가 참여자들 모두에게 커밋 여부를 확인 → 모두 OK면 커밋, 아니면 롤백
    • 3PC : 2PC의 단점을 보완해서 중간 단계 추가 (네트워크 장애 대비)
14낙관적 락과 비관적 락에 대해 설명해 주세요.보통
낙관적 락과 비관적 락은 데이터베이스에서 동시성 제어를 위한 두 가지 접근 방식으로 구현 방식에 따른 분류한 것입니다. 낙관적 락은 충돌이 드물다고 가정하고, 트랜잭션이 끝날 때까지 락을 걸지 않습니다. 대신 트랜잭션이 커밋될 때 데이터의 변경을 확인합니다. 구현 방식은 버전 필드, 타임스탬프, 해시값 등으로 구현하며 락을 걸지 않기 때문에 동시성이 높고 교착 상태에 빠지지 않지만 충돌 시 롤백이나 재시도 로직이 필요합니다. 반면 비관적 락은 충돌이 빈번하다고 가정하고, 트랜잭션이 데이터에 접근할 때마다 락을 거는 것입니다. 데이터베이스의 락 메커니즘을 사용하여 구현하며 락으로 인한 성능 저하나 교착 상태에 주의해야 하지만 충돌 처리 로직이 필요없습니다. 보통은 읽기 위주 시스템이나 웹 애플리케이션엔 낙관적 락, 실시간 정합성이 중요한 금융, 재고 시스템엔 비관적 락을 사용합니다.
상세 설명
  • 낙관적 락과 비관적 락은 데이터베이스에서 동시성 제어를 위한 두 가지 접근 방식으로 구현 방식에 따른 분류한 것이다.

낙관적 락 (Optimistic Lock)

  • 낙관적 락은 데이터 충돌이 드물다고 가정하고, 트랜잭션이 끝날 때까지 데이터에 대한 락을 걸지 않는다. 대신 트랜잭션이 커밋될 때 데이터가 변경되지 않았는지 확인한다.

  • 예를 들어, 사용자가 데이터를 읽고 수정한 후 저장할 때, 저장 시점에 데이터의 버전이 변경되지 않았는지 확인하여 충돌을 감지한다.

  • 구현 방식
    • 일반적으로 버전 필드(version), 타임스탬프(timestamp), 해시값(hash) 등을 사용하여 구현

    • JPA 구현 예시 : @Version 어노테이션을 사용하여 엔티티의 버전 필드를 관리한다.

      @Entity
      public class Product {
          @Id @GeneratedValue
          private Long id;
          private String name;
          private int price;
      
          @Version
          private Long version;
      }
      
  • 장점
    • 락을 획득하지 않기 때문에 동시성이 높고, 교착 상태(Deadlock)가 발생하지 않는다.
  • 단점점
    • 충돌 발생 시 롤백과 재시도 로직이 필요하며, 충돌이 자주 발생하면 성능이 저하된다.
  • 사용 시나리오
    • 읽기 작업이 많고 충돌이 적은 환경, 웹 애플리케이션의 동시 편집, 분산 시스템 등에 적합하다

비관적 락 (Pessimistic Lock)

  • 비관적 락은 데이터 충돌이 자주 발생한다고 가정하고, 데이터에 접근할 때마다 락을 걸어 다른 트랜잭션이 해당 데이터에 접근하지 못하도록 한다.

  • 구현 방식
    • 데이터베이스의 락 메커니즘(shared lock, exclusive lock)을 직접 사용한다.

    • JPA 구현 예시 : LockModeType을 사용하여 다양한 종류의 락을 적용할 수 있다.

      // 읽기 락(shared lock)
      Product product = entityManager.find(Product.class, id, LockModeType.PESSIMISTIC_READ);
      
      // 쓰기 락(exclusive lock)
      Product product = entityManager.find(Product.class, id, LockModeType.PESSIMISTIC_WRITE);
      
  • 장점
    • 트랜잭션이 시작될 때 데이터 일관성을 보장하기 때문에 충돌 처리 로직이 필요 없다.
  • 단점
    • 락이 오래 유지되면 동시성이 저하되고, 교착 상태가 발생할 수 있다.
  • 사용 시나리오
    • 충돌이 자주 발생하는 환경, 데이터 일관성이 매우 중요한 금융 시스템, 재고 관리 등에 적합하다

성능 비교

  • 낙관적 락은 락을 사용하지 않아 시스템 자원 소모가 적지만, 충돌 시 롤백 비용이 발생한다.
  • 비관적 락은 락을 유지하는 오버헤드가 있지만, 충돌이 자주 발생하는 경우 롤백 비용이 없어 유리할 수 있다.
15MySQL InnoDB에서 갭 락과 넥스트키 락이란 무엇이며, 어떻게 팬텀 리드를 방지하나요? ⭐어려움
MySQL의 InnoDB 저장 엔진은 트랜잭션 격리 수준이 Repeatable Read입니다. 그런데 이 수준에서도 팬텀 리드를 방지할 수 있습니다. 이는 InnoDB가 넥스트키 락이라는 독자적인 락 메커니즘을 사용하기 때문입니다. 팬텀 리드는 한 트랜잭션 내에서 동일한 WHERE 조건으로 두 번 이상 조회했을 때, 다른 트랜잭션의 INSERT로 인해 결과 집합에 없던 새로운 행이 나타나는 현상입니다. 넥스트키 락은 레코드 락과 갭 락을 결합한 상태로 레코드 락으로 트랜잭션이 완료될 때까지 WHERE 조건의 결과 레코드에 대해 변형과 삭제를 방지하고, 갭 락을 통해 결과 레코드 사이의 앞 뒤 간격에 대해서 새로운 레코드 삽입을 막기 때문에 팬텀 리드를 방지할 수 있습니다. 하지만 이러한 락이 동시성을 저하시키고, 복잡한 데드락의 주된 원인이 될 수 있습니다.
상세 설명

팬텀 리드 (Phantom Read)

-- 트랜잭션 T1 시작
BEGIN;
SELECT * FROM users WHERE age BETWEEN 20 AND 30; -- (결과: 10건)

-- 이 시점에 다른 트랜잭션 T2가 아래 쿼리를 실행하고 COMMIT한다.
-- 트랜잭션 T2 : INSERT INTO users (name, age) VALUES( 'phantom', 25 );

-- T1에서 다시 조회
SELECT * FROM users WHERE age BETWEEN 20 AND 30; 
-- (결과: 11건) 새롭게 추가된 'phantom'도 조회된다.
COMMIT;
  • Phantom Read는 대표적인 데이터베이스의 3가지 동시성 문제 중 하나로, 트랜잭션 격리 수준과 관련되어 있다.
  • Phantom Read는 동일 조건으로 두 번이상 쿼리를 실행했을 때, 첫 번째 결과에는 없던 유령(Phantom) 같은 행이 두 번째 결과에 나타나는 현상이다.
  • 이는 다른 트랜잭션이 해당 조건에 만족하는 새로운 행을 INSERT하고 커밋했을 때 발생하며, REPEATABLE READ 격리 수준의 표준 정의에 따르면 허용되는 현상이다.
  • 하지만 MySQL의 InnoDB는 이 현상을 허용하지 않는다

MySQL InnoDB에서 갭 락과 넥스트키 락이란 무엇인가?

  • MySQL의 InnoDB 저장 엔진에서, **갭 락(Gap Lock)**과 **넥스트키 락(Next-Key Lock)**은 Phantom Read를 방지하기 위한 락 메커니즘이다.

  • MySQl InnoDB는 트랜잭션 격리 수준 중 Repeatable Read 격리 수준이지만 넥스트키 락을 사용하여 Serializable 처럼 Phantom Read도 방지할 수 있는 것이다.


  • 핵심 원칙 : "잠금은 인덱스에 걸린다”

    • InnoDB의 모든 잠금은 데이터 로우(Row)가 아닌, 인덱스 레코드(Index Record)에 적용된다.
    • 쿼리가 실행될 때 MySQL 옵티마이저는 가장 효율적인 실행 계획을 위해 인덱스를 하나 선택
    • UPDATE, DELETE, SELECT ... FOR UPDATE 등의 쿼리가 실행되면, InnoDB는 옵티마이저가 선택한 인덱스를 스캔하면서 지나가는 모든 곳에 잠금을 설정한다.
    • 즉, 잠금의 범위는 WHERE 절 조건 자체가 아니라, 그 조건을 만족시키기 위해 스캔한 인덱스의 범위에 의해 결정
      • 스캔 시작 지점 탐색 (where 조건에 해당하는 위치)
      • 해당 레코드에 대해 레코드 락, 해당 레코드 바로 앞의 레코드에 갭 락 적용
      • 계속해서 뒤쪽으로 조건에 해당하는 레코드들을 레코드 락하고 앞 간격에 갭 락 적용된다.
      • 마지막 해당 레코드의 경우 바로 다음 레코드에 갭 락을 적용하여 사이 공간을 막는다.

락의 종류

  • 레코드 락 (Record Lock)

    • 인덱스 레코드에 대한 락으로, 특정 Row에만 락을 설정한다.
    • 락의 적용 범위는 사용되는 인덱스의 구조에 따라 달라진다.
    • 적절한 인덱스 조건
      • PK 또는 UK를 통해 단 하나의 레코드를 정확히 특정할 때 사용된다. (WHERE id = 10)
      • 다른 레코드나 간격에는 영향을 주지 않아 가장 효율적입니다.
    • 인덱스가 없거나 적절하지 않은 인덱스
      • 옵티마이저는 조건을 만족하는 데이터를 찾기 위해 테이블의 모든 데이터를 처음부터 끝까지 다 뒤져봐야 합니다 (Full Table Scan)
      • 아주 넓은 범위의 락이 설정되는 최악의 상황이 발생


  • 갭 락 (Gap Lock)

    • 인덱스 레코드 사이의 간격에 대한 락으로, 다른 트랜잭션이 해당 간격에 새로운 레코드가 삽입하는 것을 방지한다.

      ⇒ 즉, 조건에 따른 결과 레코드 사이의 부분을 잠금하여 이 간격 내에 새로운 레코드 삽입을 방지



  • 넥스트키 락 (Next-Key Lock)

    • 레코드 락과 갭 락을 결합한 형태(레코드 락 + 갭 락)로, InnoDB의 기본 락 방식
    • 넥스트키 락은 인덱스 레코드에 대한 레코드 락과 그 이전 간격에 대한 갭 락의 조합

팬텀 리드 방지 작동 방식

위의 잠금들을 조합하여 InnoDB는 팬텀 리드를 차단한다.

  1. 쿼리 실행 : SELECT * FROM users WHERE age BETWEEN 20 AND 30 FOR UPDATE; 실행
  2. 인덱스 스캔 : 옵티마이저는 age 컬럼의 인덱스를 스캔하기 시작
  3. 넥스트키 락 적용
    • age가 20 이상인 첫 번째 레코드부터 30 이하인 마지막 레코드까지 스캔하면서, 마주치는 모든 인덱스 레코드와 그 사이의 간격에 넥스트키 락을 설정
    • 예를 들어 age 인덱스에 19, 22, 28, 32 값이 있다면, 쿼리는 age=22와 age=28 레코드를 잠글 뿐만 아니라, (19, 22], (22, 28], (28, 32] 구간의 간격까지 모두 잠근다.
  4. 삽입 차단
    • 이 상태에서 다른 트랜잭션이 INSERT INTO users (..., age) VALUES (..., 25); 를 시도하면, age=25는 (22, 28] 간격에 해당한다. 이 간격은 이미 첫 번째 트랜잭션의 갭 락에 의해 잠겨 있으므로, INSERT 트랜잭션은 대기 상태에 빠진다.
  5. 정합성 보장
    • 첫 번째 트랜잭션이 커밋되거나 롤백될 때까지 age 20~30 범위에는 어떤 새로운 데이터도 들어올 수 없다.
    • 이로써 팬텀 리드가 원천적으로 방지된다.

실무적 관점

  • 성능 영향

    • 넥스트키 락은 높은 수준의 일관성을 제공하지만, 동시성을 저하시킬 수 있다.
    • 데드락의 주된 원인 될 수 있다.

  • 락 모드 조정

    • MySQL 5.7부터 SELECT ... FOR SHARE와 SELECT ... FOR UPDATE의 옵션으로 NOWAIT 또는 SKIP LOCKED를 사용하여 락 대기 동작을 조정할 수 있다.

데드락은 왜 발생하는가?

  • 데드락은 둘 이상의 트랜잭션이 서로가 점유한 리소스(잠금)를 기다리면서 무한 대기 상태에 빠지는 현상이다. InnoDB의 넥스트키 락 환경에서는 다음과 같은 상황에서 주로 발생한다.
  • 상호 배타적인 잠금 경로
    • 두 트랜잭션이 서로 다른 순서로 잠금을 요청할 때 발생합니다.
  • 복수의 인덱스 동시 수정
    • UPSERT와 같은 작업은 UK(Unique Key)로 중복을 체크하고, PK로 데이터를 삽입하며, 다른 세컨더리 인덱스들도 동시에 수정한다. 이 과정에서 여러 인덱스에 동시다발적으로 잠금을 걸다가 다른 트랜잭션과 경로가 꼬이기 쉽다.

데드락 발생 시나리오 예시

두 스레드가 병렬로 UPSERT(INSERT ... ON DUPLICATE KEY UPDATE)와 DELETE를 실행한다고 가정해봅시다.

  • 트랜잭션 A (DELETE)

    • DELETE FROM holidays WHERE year = 2025 AND country = 'US';를 실행
    • idx_country_year 인덱스를 넓게 스캔하며 조건에 맞는 범위에 넥스트키 락을 설정

  • 트랜잭션 B (UPSERT)

    • INSERT INTO holidays (date, name, country, year) VALUES ('2025-12-25', 'Christmas', 'US', 2025) ON DUPLICATE KEY UPDATE ...;를 실행
    • 먼저 중복 체크를 위해 uk_date_name(유니크 키)에 잠금을 시도
    • 동시에, 삽입될 위치의 PK 인덱스와 idx_country_year 인덱스에도 잠금이 필요

  • 교착 상태

    • 트랜잭션 A가 idx_country_year의 넓은 범위를 잠근 상태에서, 트랜잭션 B가 uk_date_name을 잠그고 idx_country_year의 특정 지점에 잠금을 걸려고 한다.
    • 이때, 트랜잭션 A가 트랜잭션 B가 잡고 있는 uk_date_name 또는 PK와 관련된 다른 리소스를 필요로 하게 되면 순환 대기가 발생하여 InnoDB가 한쪽 트랜잭션을 롤백시키며 데드락 에러를 발생시킨다.

데드락 해결 및 예방을 위한 아키텍처 전략

데드락은 일시적인 현상이므로 재시도만으로 해결할 수도 있지만, 근본적인 발생 원인을 제거하는 것이 중요하다.


  • 데이터 정렬

    • 가장 간단하고 효과적인 방법이지만, 완벽한 해결은 아니다.
    • 대량의 데이터를 INSERT하거나 UPDATE할 때, PK나 UK 같은 고유 키 기준으로 데이터를 미리 정렬한 후 처리하는 것
    • 모든 트랜잭션이 항상 동일한 순서(ex. 오름차순)로 인덱스 레코드를 접근하게 되므로, 잠금 획득 순서가 꼬일 확률이 줄어든다.

작업 분리 (Separating Workloads)

데드락의 원인이 복잡한 작업들의 동시 실행에 있다면, 작업을 분리하는 것이 좋습니다.

  • 1단계: 병렬 조회 및 준비: 외부 API 호출, 데이터 파싱 등 DB와 무관한 작업들은 병렬로 최대한 처리하여 메모리에 준비합니다.
  • 2단계: 순차 쓰기: 모든 데이터가 준비되면, 단일 스레드, 단일 트랜잭션으로 DB에 쓰는 작업을 순차적으로 실행합니다. (예: 모든 UPSERT 실행 후, 마지막에 DELETE 한 번 실행)
  • 효과: DB에 쓰기 작업을 하는 스레드가 오직 하나뿐이므로, 트랜잭션 간의 잠금 경합 자체가 사라져 데드락이 원천적으로 발생하지 않습니다.

  • 최종 안전망: 재시도 로직 (@Retryable)
    • 아키텍처를 완벽하게 구축했더라도, 네트워크 문제나 예상치 못한 데드락이 발생할 수 있다.
    • 데드락은 재시도 시 성공할 가능성이 높은 대표적인 일시적 오류이므로, Spring의 @Retryable 애노테이션 같은 메커니즘을 활용하여, 데드락이나 관련 예외 발생 시 자동으로 작업을 재시도하는 최종 안전망을 구축하는 것이 좋다.

JDBC 드라이버 최적화

  • 순차 쓰기 전략의 성능을 극대화하기 위해, MySQL JDBC 연결 URL에 rewriteBatchedStatements=true 옵션을 추가하는 것을 권장된다.
  • 이 옵션은 여러 개의 INSERT 쿼리를 하나의 거대한 multi-value INSERT 쿼리로 재작성하여 DB에 전송함으로써, 네트워크 통신 오버헤드를 획기적으로 줄여줄 수 있다.