에디블로그
Engineer's Field Notes

AI 자동화로 매일
한 편씩 쓰는
엔지니어 운영 노트

Claude Code · 자동화 파이프라인 · 사고 회고까지. 잘 굴러간 기록 + 깨진 흔적도 같이 남깁니다.

사람이 할 수 있는 일은,
AI도 할 수 있어야 합니다.
매일 한 편 쓰면서 검증 중.
— 이번 주 가장 많이 읽힌 글 TOP 3
백엔드/데이터 & DB

[MySQL] 데드락 원인과 해결: SHOW ENGINE INNODB STATUS 읽는 법, 재시도 전략

반응형
[MySQL] 데드락 원인과 해결: SHOW ENGINE INNODB STATUS 읽는 법, 재시도 전략

[MySQL] 데드락 원인과 해결: SHOW ENGINE INNODB STATUS 읽는 법, 재시도 전략

운영 알림에 Deadlock found when trying to get lock이 뜨면 심장이 철렁하죠. 그런데 데드락은 미스터리가 아니에요 — 원인 패턴이 몇 개 안 되고 MySQL이 친절하게 "누가 누구와 어떻게 엉켰는지" 로그까지 남겨줘요. 읽는 법만 알면 돼요.

데드락이 생기는 구조, 실무에서 자주 보는 패턴 둘(역순 잠금·갭 락+INSERT), SHOW ENGINE INNODB STATUS 읽는 법, 예방 체크리스트와 재시도 전략까지 차례로 풀어요. InnoDB 락 편의 락 종류를 알고 보면 훨씬 잘 보여요.

01. 데드락의 구조, 원형 대기

데드락 원형 대기 구조. 트랜잭션 A가 행 1을 잡고 행 2를 기다리는데 B는 행 2를 잡고 행 1을 기다려 서로 영원히 대기한다. InnoDB가 즉시 감지해 한쪽을 강제 롤백하며 1213 에러를 던진다

A는 행 ①을 잡고 ②를 기다려요. B는 ②를 잡고 ①을 기다려요. 누구도 양보할 수 없는 원형 대기예요. 다행히 InnoDB는 이걸 즉시 감지해서 한쪽(보통 롤백 비용이 적은 쪽)을 강제 롤백시켜요. 그래서 데드락은 "무한 멈춤"이 아니라 "한쪽의 1213 에러"로 나타나요.

이 사실에서 중요한 결론이 나와요 — 데드락의 기본 대응은 재시도예요. 희생된 트랜잭션을 다시 실행하면 (상대는 이미 끝났으니) 대부분 성공해요. 에러를 그대로 사용자에게 던지는 게 가장 나쁜 처리예요.

02. 단골 패턴 ①, 역순 잠금

실무 데드락 단골 패턴. 역순 잠금은 두 로직이 반대 순서로 자원을 갱신해 생기며 잠금 순서 통일이 해법이고, 갭 락과 INSERT 조합은 같은 범위를 조회한 두 트랜잭션이 서로의 틈 INSERT를 막아 생긴다

가장 고전적인 패턴이에요. 주문 로직은 "주문 → 재고" 순으로 UPDATE해요. 재고 정산 배치는 "재고 → 주문" 순으로 UPDATE하면, 둘이 겹치는 순간 서로의 두 번째 자원을 기다리며 엉켜요.

해법은 잠금 순서 통일이에요. "여러 자원을 갱신할 땐 항상 같은 순서로"라는 팀 규칙을 만드는 거예요. 여러 행을 한 번에 잠글 때도 마찬가지예요 — WHERE id IN (...)으로 여러 행을 갱신하는 로직이 둘 있다면, 둘 다 ID 오름차순으로 잠그게 정렬해두면 역순 엉킴이 사라져요.

03. 단골 패턴 ②, 갭 락 + INSERT

더 당황스러운 패턴이에요. 같은 행을 안 건드렸는데 데드락이 나요.

A와 B가 둘 다 "이 값이 있나" 범위 조회(잠그는 읽기)를 해요. 둘 다 같은 갭에 갭 락을 잡아요(갭 락끼리는 공존 가능해요). 그다음 둘 다 그 틈에 INSERT를 시도하면 — 각자의 INSERT가 상대의 갭 락에 막혀요. 서로가 서로를 기다리는 원형 대기 완성이에요.

"존재 확인 후 INSERT" 패턴(중복 체크 후 가입 처리 등)에서 동시 요청이 몰리면 이게 터져요. 해법은 패턴 자체를 바꾸는 거예요 — 유니크 제약을 걸고 INSERT 후 중복 에러를 처리하거나, upsert(INSERT ... ON DUPLICATE KEY UPDATE)로 한 문장으로 끝내요. "읽고 판단하고 쓰기"를 DB의 원자적 한 방으로 바꾸는, 멱등성 글에서 본 그 사고방식이에요.

04. SHOW ENGINE INNODB STATUS 읽는 법

데드락이 나면 MySQL이 마지막 데드락의 전말을 보관해요.

SHOW ENGINE INNODB STATUS\G
-- 출력에서 "LATEST DETECTED DEADLOCK" 섹션을 찾는다

그 섹션에 이렇게 나와요.

  • (1) TRANSACTION — 첫 번째 트랜잭션이 실행하던 SQL
  • (1) WAITING FOR THIS LOCK TO BE GRANTED — 걔가 기다리던 락 (어느 인덱스의 어느 레코드인지)
  • (2) TRANSACTION / HOLDS THE LOCK(S) / WAITING FOR — 두 번째 트랜잭션이 쥔 락과 기다린 락
  • WE ROLL BACK TRANSACTION (N) — 누가 희생됐는지

읽는 요령은 "각자 무슨 SQL을 하다가, 어느 인덱스 레코드를 기다렸나"를 맞춰보는 거예요. lock_mode X locks gap before rec 같은 문구가 보이면 갭 락 패턴이고, 서로 다른 테이블을 반대로 기다리면 역순 잠금이에요. 두 SQL을 알면 코드에서 그 두 경로를 찾는 건 금방이에요.

운영 팁 — 이 출력은 "마지막 데드락 1건"만 보관해요. 자주 나는데 패턴이 여러 개면 놓쳐요. innodb_print_all_deadlocks = ON을 켜면 모든 데드락이 에러 로그에 남아서 빈도와 패턴 분석이 가능해져요. 데드락 알림이 반복되면 제일 먼저 켤 스위치예요.

05. 예방 체크리스트와 재시도

데드락 대응 체크리스트. 잠금 순서 통일, 트랜잭션 짧게, WHERE 컬럼 인덱스, 잠금 범위 최소화로 빈도를 줄이고, 그래도 발생하는 데드락은 재시도로 무해하게 처리한다
  • 잠금 순서 통일 — 역순 잠금 차단. 다중 행은 ID 정렬 후 갱신.
  • 트랜잭션 짧게 — 락을 쥔 시간이 짧을수록 겹칠 확률이 줄어요. 외부 API 호출을 트랜잭션 안에 두는 게 최악이에요.
  • WHERE에 인덱스 — 인덱스 없는 UPDATE의 풀 스캔 잠금은 데드락 확률을 폭증시켜요(앞 편의 그 함정).
  • 잠금 범위 최소화 — 갭 락 경합을 줄여요. 유니크 인덱스 동등 조건은 갭 락 없이 레코드 락만 잡아요.

그리고 받아들일 것 하나 — 데드락은 0이 안 돼요. 동시성 있는 시스템의 자연 현상이에요. 1213 에러는 짧은 대기 후 트랜잭션 전체를 재실행하는 재시도 로직으로 흡수해요. 스프링이면 @Retryable이나 AOP로 공통화하되, 주의점은 낙관적 락 재시도와 같아요 — 이미 롤백 마킹된 트랜잭션 안에서 잡지 말고 트랜잭션 밖에서 새로 시작해야 해요. 재시도할 로직은 멱등해야 하고요.

06. 자주 만나는 문제

같은 행을 안 건드렸는데 데드락이 나요

갭 락 + INSERT 패턴이에요. STATUS 출력에 gap 락이 보이면 확정이에요. "확인 후 INSERT"를 upsert·유니크 제약 방식으로 바꿔요.

특정 배치가 돌 때마다 서비스에서 데드락이 나요

배치와 실시간 로직의 잠금 순서가 다른 거예요. 배치의 갱신 순서를 실시간 로직과 통일하거나, 배치를 작은 청크로 쪼개 락 점유를 짧게 해요.

데드락이 너무 자주 나요

빈도가 높으면 재시도로 덮을 단계가 아니에요. innodb_print_all_deadlocks로 패턴을 모아 가장 잦은 한두 패턴부터 구조적으로(순서 통일·범위 축소) 제거해요.

정리

데드락은 원형 대기이고 InnoDB가 감지해 한쪽을 롤백하는 1213 에러로 나타나요. 원인은 대부분 역순 잠금 아니면 갭 락+INSERT이고 SHOW ENGINE INNODB STATUS가 전말을 알려줘요. 줄이고 흡수하는 손은 네 가지예요.

  • 잠금 순서 통일 — 다중 행은 ID 정렬 후 갱신해 역순 엉킴을 차단.
  • 트랜잭션 짧게 — 외부 API 호출을 트랜잭션 안에 두지 않기.
  • WHERE에 인덱스 — 풀 스캔 잠금이 데드락 확률을 폭증시키니까.
  • 남는 건 재시도 — "제로"가 아니라 "드물고 무해하게"가 목표예요.

여기까지가 락의 세계였어요. 이제 무대를 옮겨, 애플리케이션과 DB 사이의 관문 — HikariCP 커넥션 풀로 들어갑니다.

출처: MySQL — Deadlocks in InnoDB · MySQL — Deadlock Detection

반응형

📚 같이 보면 좋은

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 일정액의 수수료를 제공받습니다."