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=22age=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 SHARESELECT ... 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에 전송함으로써, 네트워크 통신 오버헤드를 획기적으로 줄여줄 수 있다.