MySQL 운영 중 deadlock found when trying to get lock 에러를 보면 처음에는 lock wait timeout과 비슷하게 느껴질 수 있습니다. 하지만 deadlock은 단순히 “오래 기다렸다”가 아니라, 서로가 서로를 기다리는 순환 구조가 생겼다는 점에서 조금 다릅니다.
이 글에서는 아래 내용을 정리합니다.
- deadlock이 무엇인지
- lock wait timeout과 무엇이 다른지
- 어떤 상황에서 자주 생기는지
- 어떤 순서로 원인을 추적해야 하는지
핵심은 deadlock은 느린 쿼리 하나의 문제가 아니라, 여러 트랜잭션이 서로 다른 순서로 자원에 접근하면서 생기는 충돌 구조라는 점입니다.
deadlock이란 무엇인가
deadlock은 두 개 이상의 트랜잭션이 서로가 가진 잠금을 기다리면서 더 이상 진행할 수 없는 상태를 말합니다.
예를 들어:
- 트랜잭션 A는 row 1을 잠근 뒤 row 2를 기다림
- 트랜잭션 B는 row 2를 잠근 뒤 row 1을 기다림
이러면 둘 다 앞으로 갈 수 없습니다. MySQL은 이 상태를 감지하면 보통 한쪽 트랜잭션을 희생시켜 롤백합니다.
lock wait timeout과는 무엇이 다를까
둘 다 잠금 문제지만 성격이 다릅니다.
lock wait timeout: 한쪽이 오래 기다리다가 제한 시간을 넘김deadlock: 서로가 서로를 기다리는 순환 대기 구조
즉, deadlock은 기다림의 시간이 길어서가 아니라 구조적으로 풀리지 않는 상태라서 바로 개입이 필요합니다.
어떤 상황에서 자주 생길까
대표적인 경우는 아래와 같습니다.
- 여러 트랜잭션이 같은 row 집합을 다른 순서로 갱신
- 배치 작업과 실시간 요청이 같은 테이블을 동시에 수정
- 트랜잭션 안에서 너무 많은 row를 건드림
- 인덱스가 좋지 않아 잠금 범위가 넓어짐
즉, 쿼리 자체보다도 “접근 순서”와 “트랜잭션 범위”가 매우 중요합니다.
무엇부터 확인할까
deadlock을 보면 먼저 아래를 확인하는 흐름이 좋습니다.
- 에러가 난 쿼리와 트랜잭션 흐름 확인
- 어떤 테이블과 row 집합이 관련됐는지 확인
- 서로 다른 코드 경로가 같은 자원에 다른 순서로 접근하는지 확인
- 트랜잭션이 불필요하게 길지 않은지 확인
중요한 것은 에러 로그 한 줄보다, 그 앞뒤에서 어떤 작업들이 동시에 들어왔는지를 같이 보는 것입니다.
왜 접근 순서가 중요할까
deadlock은 같은 자원을 건드려도 순서를 통일하면 크게 줄어드는 경우가 많습니다.
예를 들어 두 트랜잭션이 항상:
- 사용자 row
- 주문 row
순서로만 접근한다면 충돌 구조가 단순해집니다. 반대로 어떤 경로는 주문부터, 어떤 경로는 사용자부터 잠그면 순환 대기가 생기기 쉬워집니다.
트랜잭션을 짧게 가져가야 하는 이유
트랜잭션이 길어질수록 잠금을 오래 들고 있게 되고, deadlock이 생길 확률도 높아집니다.
특히 아래는 위험합니다.
- 트랜잭션 안에서 외부 API 호출
- 여러 비즈니스 로직을 한 트랜잭션에 몰아넣기
- 한 번에 너무 많은 row 업데이트
자주 하는 오해
1. deadlock은 DB가 불안정해서 생긴다
많은 경우 애플리케이션의 접근 순서와 트랜잭션 설계 문제입니다.
2. deadlock이 가끔 나도 무시해도 된다
낮은 빈도라도 중요한 경로에서 발생하면 사용자 실패로 바로 이어질 수 있습니다.
3. 재시도만 붙이면 끝난다
재시도는 완화책일 수 있지만, 원인 구조를 그대로 두면 반복됩니다.
FAQ
Q. deadlock은 완전히 없앨 수 있나
항상 0으로 만들기는 어렵지만, 접근 순서와 트랜잭션 범위를 정리하면 크게 줄일 수 있습니다.
Q. deadlock이 나면 무엇을 먼저 바꿔야 하나
트랜잭션 안에서 접근하는 자원 순서와 범위를 먼저 점검하는 것이 좋습니다.
Q. retry는 필요 없나
필요할 수 있습니다. 다만 retry만으로는 구조적 문제를 해결하지 못합니다.
Read Next
- 잠금 대기 전반을 먼저 정리하려면 MySQL lock wait timeout 가이드를 같이 읽어보세요.
- 쿼리 접근 경로와 인덱스 문제는 MySQL slow query 가이드와 자연스럽게 이어집니다.
심사 대기 중에는 광고 대신 관련 가이드를 먼저 보여줍니다.
먼저 읽어볼 가이드
검색 유입이 많은 핵심 글부터 이어서 보세요.
- 미들웨어 트러블슈팅 가이드: Redis vs RabbitMQ vs Kafka 개발자를 위한 미들웨어 트러블슈팅 허브 글입니다. Redis, RabbitMQ, Kafka 중 어떤 증상부터 먼저 봐야 하는지와 어떤 문제 패턴이 각 시스템에 가까운지 정리합니다.
- Kubernetes CrashLoopBackOff: 먼저 볼 것들 startup failure, probe, config, resource limit 관점에서 CrashLoopBackOff를 어떻게 나눠서 봐야 하는지 정리한 가이드입니다.
- Kafka consumer lag가 계속 늘 때: 트러블슈팅 가이드 Kafka consumer lag가 계속 늘어날 때 무엇부터 봐야 하는지 정리합니다. poll 주기, 처리 속도, rebalance, consumer 설정까지 실전 기준으로 다룹니다.
- Kafka Rebalancing Too Often 가이드 Kafka consumer group에서 rebalance가 너무 자주 일어날 때 membership flapping, poll timing, protocol, assignment churn을 어떤 순서로 봐야 하는지 설명하는 실전 가이드입니다.
- Docker container가 계속 재시작될 때: 먼저 확인할 것들 exit code, command failure, environment mistake, health check 관점에서 Docker restart loop를 푸는 실전 가이드입니다.
심사 대기 중에는 광고 대신 관련 가이드를 먼저 보여줍니다.