[MySQL] 데드락 원인과 해결: SHOW ENGINE INNODB STATUS 읽는 법, 재시도 전략
![[MySQL] 데드락 원인과 해결: SHOW ENGINE INNODB STATUS 읽는 법, 재시도 전략](https://blog.kakaocdn.net/dna/P3tbt/dJMcahkqzGc/AAAAAAAAAAAAAAAAAAAAAKBo14imzSwRpI4MZevZ6rwuo_H8QZSM0jgBEY-5cDyU/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1788188399&allow_ip=&allow_referer=&signature=0djtw0wduRa3TSu3Rw2Um%2Be4xsE%3D)
[MySQL] 데드락 원인과 해결: SHOW ENGINE INNODB STATUS 읽는 법, 재시도 전략
운영 알림에 Deadlock found when trying to get lock이 뜨면 심장이 철렁하죠. 그런데 데드락은 미스터리가 아니에요 — 원인 패턴이 몇 개 안 되고 MySQL이 친절하게 "누가 누구와 어떻게 엉켰는지" 로그까지 남겨줘요. 읽는 법만 알면 돼요.
데드락이 생기는 구조, 실무에서 자주 보는 패턴 둘(역순 잠금·갭 락+INSERT), SHOW ENGINE INNODB STATUS 읽는 법, 예방 체크리스트와 재시도 전략까지 차례로 풀어요. InnoDB 락 편의 락 종류를 알고 보면 훨씬 잘 보여요.
01. 데드락의 구조, 원형 대기

A는 행 ①을 잡고 ②를 기다려요. B는 ②를 잡고 ①을 기다려요. 누구도 양보할 수 없는 원형 대기예요. 다행히 InnoDB는 이걸 즉시 감지해서 한쪽(보통 롤백 비용이 적은 쪽)을 강제 롤백시켜요. 그래서 데드락은 "무한 멈춤"이 아니라 "한쪽의 1213 에러"로 나타나요.
이 사실에서 중요한 결론이 나와요 — 데드락의 기본 대응은 재시도예요. 희생된 트랜잭션을 다시 실행하면 (상대는 이미 끝났으니) 대부분 성공해요. 에러를 그대로 사용자에게 던지는 게 가장 나쁜 처리예요.
02. 단골 패턴 ①, 역순 잠금

가장 고전적인 패턴이에요. 주문 로직은 "주문 → 재고" 순으로 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. 예방 체크리스트와 재시도

- 잠금 순서 통일 — 역순 잠금 차단. 다중 행은 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
'백엔드 > 데이터 & DB' 카테고리의 다른 글
| [MySQL] 느린 쿼리 트러블슈팅 가이드: slow query log, 락 대기, 커넥션 고갈 증상별 대응 (0) | 2026.07.28 |
|---|---|
| [MySQL] HikariCP 커넥션 풀 설정: 풀 사이즈 공식, 타임아웃, 커넥션 누수 잡기 (0) | 2026.07.27 |
| [MySQL] InnoDB 락 정리: 레코드 락, 갭 락, 넥스트키 락, SELECT FOR UPDATE (0) | 2026.07.23 |
| [MySQL] 트랜잭션 격리수준과 MVCC: REPEATABLE READ, 언두 로그, 팬텀 리드 (0) | 2026.07.22 |
| [MySQL] EXPLAIN 실행계획 읽는 법: type, Extra, rows 그리고 EXPLAIN ANALYZE (0) | 2026.07.21 |
📚 같이 보면 좋은
"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 일정액의 수수료를 제공받습니다."