[TIL] Real MySQL DEAD LOCK

2025.03.28
·
11 min read

데드락?

  • DB 에서 2개 이상의 트랜잭션이 서로 자원을 기다리며 무한 대기 상태에 빠지는 상황

언제 발생하는지

  • 상호 잠금 경합이 발생하는 경우 나타난다.

  • 트랜잭션이 완료되지 못하고 대기 상태에 머물러서 서비스 지연을 초래할 수 잇음.

트랜잭션

  • DB에서의 하나의 논리적 작업 단위이다.

  • 여러 작업이 모두 성공하거나 모두 실패해야 하는 특성을 가짐. (원자성)

    • A → B 송금 ( 1. A의 잔액 차감 2. B의 잔액 증가 )

데드락의 예시

은행 전자 지갑 서비스

  • 시나리오

    • A → B

      • 100 원 송금

    • B → A

      • 500 원 송금

    • 트랜잭션 진행

      1. 트랜잭션 1

        • A 전자지갑 레코드 → 배타적 잠금(Exclusive Lock) 을 걸고 100원 차감

        배타적 잠금(Exclusive Lock)

        • 특정 자원에 대한 독점적인 접근 권한 부여하는 잠금

        • 다른 트랜잭션이 접근조차 못함

        • 읽기는 쓰기든 접근을 못하는 거임

      2. 트랜잭션 2

        • B 사용자 전자지갑 레코드에 배타적 잠금 을 걸고 500원을 차감한다.

      3. 트랜잭션 1

        • B 사용자의 레코드에 100월 추가하려고하지만…

        • 트랜잭션 2 가 이미 잠금을 걸어 대기한다.

      4. 트랜잭션 2

        • A 사용자의 레코드에 500원을 추가하려고 시도하면

        • 트랜잭션 1 이 이미 잠금을 걸었기에 대기를 시도함.

      • 결과적으로

        • 데드락 발생

    공유 잠금(Shared Lock)

    • 여러 트랜잭션이 동시에 자원에 읽기 접근을 허용함.

      • 다만

        • 쓰기는 불가능하다.

데드락 회피 방법?

사용자 ID 순서로 처리

  • 트랜잭션의 작업 순서를 일정하게 유지하는 방법

방법

  • 작은 id 먼저 규칙을 적용한다 ( 큰 ID 먼저 해도 노상관임 )

    • a ( PK 1001 ) < b( PK 1002 )

      • 모든 트랜잭션이 A → B 순서로 작업된다.

    • 트랜잭션 1 ( A → B 송금 )

      1. UPDATE accounts SET balance = 8000 WHERE id = 1001;

        1. A 잠금 → 잔액 10,000 에서 - 2,000 ⇒ 8,000 이 된다

      2. UPDATE accounts SET balance = 7000 WHERE id = 1002;

        1. B 잠금 → 잔액 5,000 + 2,000 ⇒ 7,000

      3. 커밋 → A , B 잠금이 해제된다.

    • 트랜잭션 2 ( B → A 송금 )

      • 트랜잭션 1이 끝날 때까지 대기 (A가 먼저 잠겨야 하니까).

      1. UPDATE accounts SET balance = 9000 WHERE id = 1001;

        1. (A 잠금 → 잔액 8,000 + 1,000 = 9,000)

      2. UPDATE accounts SET balance = 6000 WHERE id = 1002;

        1. (B 잠금 → 잔액 7,000 - 1,000 = 6,000)

      3. 커밋

    • 결국

      • B → A

      • A → B 같은

        • 데드락 경합이 발생하는 상황 자체가 없을 수 있음.

    • 단점

      • 병렬 처리가 불가능해진다.

        • 자원 잠금을 순차적으로 강제하기 때문이라는데

        • 사실 이건 잘 모르겠다 ㅠ..

MySQL 서버의 데드락 디텍션 방법

  • InnoDB 스토리지 엔진의 경우 데드락을 감지하고 해결하는 기능을 제공한다.

데드락 데텍션 스레드

  • 주기적으로 잠금 그래프를 스캔해 데드락을 탐지한다.

  • 데드락이 발견되는 경우

    • 롤백이 쉬운 트랜잭션 을 강제로 종료한다.

  • 어떤 기준으로 롤백이 쉬운 트랜잭션을 정의하냐고 하면

    • Undo 레코드 의 개수가 적은 트랜잭션을 선택한다고 한다.

Undo 레코드

어떻게 파일로 저장되는가?

-- 데이터 INSERT
INSERT INTO member(m_id, m_name, m_area) VALUES (12, '홍길동', '서울');
COMMIT;

-- 데이터 UPDATE
UPDATE member SET m_area='경기' WHERE m_id=12;
4474
  • 이렇게 데이터 업데이트 전에 undo 영역을 두고

    • 이전 값을 기록하는 임시테이블을 가지는 것이다.

데드락 디텍션 비활성화 방법

  • 옵션

    • innodb_deadlock_detectOFF 로 설정하면 디텍션 스레드가 비활성화 된다.

  • 구글 사례

    • 구글 일부 서비스에서는

      • PK 기반의 DML(INSERT, UPDATE, DELETE) 와 SELECT 만 사용한다.

      • 업무 로직과 잠금이 단순해서 데드락의 발생 빈도가 매우 낮다고 판단되는 경우

      • 디텍션 스레드로 인하여 성능 손실이 더 큰 문제로 작용한다.

        • 이러한 판단으로 비활성화 를 선택한 사례도 있다고 한다.

    • 비활성화를 하는 경우

      • 데드락 디텍션이 없어서 데드락이 발생하는 경우 트랜잭션이 무한 대기 할 것 같지만

      • innodb_lock_wait_timeout 설정값(기본 50초) 만큼 대기하고 timeout 에러가 발생한다.

  • 그렇기에

    • 기본 타임아웃은 너무 기렁서

    • 실무에서는

      2~ 3초 정도

      로 짧게 설정하여 빠르게 실패하고 재처리할 수 있도록 권장한다고 말한다.

데드락의 복잡성..

  • 데드락 자체는 서로의 리소스를 기다리는 상황을 넘어서, 다양한 요인을 발생해서

    • 데드락을 무조건적으로 전부 해결해야하는 건 아니라고 한다.

어떤 상황에서 발생하는지

  1. 삭제된 레코드와 잠금의 경우

    • MySQL 의 InnoDB 스토리지 엔진은 레코드를 삭제하는 경우 즉시 물리적으로 제거하지 않는다.

      • 삭제 표시(deleted mark) 를 설정한 상태로 유지한다.

    • Undo 로그 를 통해 트랜잭션 롤백을 지원하기 위한 설계이다.

    • 삭제 표시된 레코드는 사용자 쿼리에서 보이지 않는다.

      • 다만..

      • InnoDB 내부에서 여전히 존재하는데

        • 이는 잠금의 대상이 될 수 있다.

    • 예를 들어

      • 트랜잭션 1 이 레코드를 삭제하고 삭제 표시는 남기는 경우

      • 트랜잭션 2 + 3 이 해당 위치에 새 레코드를 삽입하려고 하는 경우

        • 삭제된 레코드의 잠금을 기다리게 되어서

        • 데드락이 발생할 가능성이 있다.

      여기서 .. 아주 얕은 지식으로

      • 엥 어떻게 삭제표시된 레코드에 .. 새 레코드를 삽입이 가능한거지..? 라고 생각했는데

      • 레코드 자체는 데이터 페이지 내의 특정 위치(슬롯) 을 차지하고 있다고 한다.

        • InnoDB 는 삭제된 레코드가 있는 공간을 재사용하려고 시도할 수도 있다고 한다.

        • PK 가 1, 2, 3 인 레코드 중에 2가 삭제된 경우

          • 새로 삽입되는 PK 값 4의 경우 기존의 삭제된 슬롯을 재사용할 가능성이 있다고 함.

            • 즉 삭제되었다고 판단된 PK 2의 공간을 재사용하는 것임.

해결..

  1. 원인 제거하는 방법

  2. 재처리 로직 추가하는 방법이 있음

데드락 발생 자체는 코드 오류나 개발자의 실수를 의미하지 않는 다는 것을 명심

재시도 로직 추가는 코드 품질 저하가 아니라 실용적인 대처 방법 이다.

잠금의 유형

  • 여러 종류의 잠금을 사용하는 데..

    • 레코드 잠금

      • 특정 레코드 잠금

    • 갭락(Gap Lock)

      • 인덱스 레코드 사이의 간격을 잠금. ( 범위 조건 등 )

    • Next Key Lock

      • 레코드와 그 앞 간격을 함께 잠금

    • Auto-Increment Lcok

      • 자동 증가 값 사용시 + INSERT 작업 시 적용되는 잠금이다.

잠금 라이프 사이클

  • 잠금의 경우 생성과 해제 시점이 다르다.

  • 유지 기간도 잠금 유형에 따라 달라지는데

    1. 레코드 잠금

      1. 트랜잭션이 종료될 떄까지 유지된다.

    2. Auto-Increment Lock의 경우

      1. INSERT 문장이 실행되는 동안만 유지된다.







- 컬렉션 아티클