MySQL ERROR 1205 Lock wait timeout 실전 진단: 락 대기 vs 데드락 구분법
운영 중인 서비스에서 갑자기 트랜잭션이 멈추고 다음 메시지가 떴다면, 이 글은 정확히 당신을 위한 것입니다.
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction메시지 끝에 try restarting transaction이 붙어 있다고 해서 진짜로 재시도만 반복하면, 근본 원인은 그대로 남아 같은 장애가 끝없이 재발합니다. 그리고 한 가지 더 — 예전에 다룬 'PostgreSQL too many clients'(커넥션 풀 고갈, 즉 연결 자원이 부족한 문제)와 이 글은 각도가 완전히 다릅니다. 여기서는 연결은 멀쩡히 살아있는데 트랜잭션들이 서로의 락을 기다리느라 멈춰버린 상황을 다룹니다.
1205와 1213은 전혀 다른 문제다
가장 먼저 할 일은 두 에러 문자열을 나란히 놓고 구분하는 것입니다.
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction| 구분 | ERROR 1205 (락 대기) | ERROR 1213 (데드락) |
|---|---|---|
| SQLSTATE | HY000 | 40001 |
| 발생 메커니즘 | innodb_lock_wait_timeout(기본 50초)이 지나도록 락을 못 얻음 | InnoDB가 순환 대기를 즉시 감지 |
| 처리 방식 | 대기하던 한 트랜잭션만 타임아웃 실패 | InnoDB가 비용이 적은 한쪽을 victim으로 즉시 롤백 |
| 발생까지 시간 | 타임아웃(수십 초)을 기다림 | 즉시(밀리초) |
| 핵심 신호 | "느리게 실패한다" | "한쪽이 바로 죽는다" |
핵심은 이렇습니다. 1205는 누군가 락을 너무 오래 잡고 안 놔줘서 발생하는 "단방향 대기"이고, 1213은 두 트랜잭션이 서로의 락을 마주 잡아 순환이 생긴 "양방향 교착"입니다. 1205가 떴다면 "누가 락을 오래 쥐고 있나"를 추적하고, 1213이 떴다면 "어떤 두 쿼리가 잠금 순서를 충돌시켰나"를 봐야 합니다.
직접 재현해 보기 (Session A / B)
개념을 머리로만 이해하면 현장에서 헷갈립니다. 두 세션을 열어 직접 만들어 봅시다.
-- 준비
CREATE TABLE acct (id INT PRIMARY KEY, balance INT);
INSERT INTO acct VALUES (1,100),(2,100);(A) 락 대기 → 1205 재현
-- Session A
BEGIN;
UPDATE acct SET balance = balance - 10 WHERE id = 1; -- id=1 락 보유, 커밋 안 함
-- Session B (A가 안 놔주면 50초 후 실패)
BEGIN;
UPDATE acct SET balance = balance + 10 WHERE id = 1;
-- ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction(B) 데드락 → 1213 재현
-- Session A
BEGIN;
UPDATE acct SET balance = balance - 10 WHERE id = 1; -- id=1 잠금
-- Session B
BEGIN;
UPDATE acct SET balance = balance - 10 WHERE id = 2; -- id=2 잠금
-- Session A: 이제 id=2를 원함 (대기)
UPDATE acct SET balance = balance + 10 WHERE id = 2;
-- Session B: 이제 id=1을 원함 → 순환 발생!
UPDATE acct SET balance = balance + 10 WHERE id = 1;
-- ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction순서만 다를 뿐인데 결과가 1205와 1213으로 갈립니다. 잠금 순서를 양쪽 세션이 똑같이(항상 id 작은 것부터) 맞추면 데드락은 사라집니다. 이게 뒤에 나올 재발 방지의 핵심입니다.
5분 진단: 블로킹 트랜잭션 추적 3종 세트
MySQL 8.0부터는 구식 INNODB_LOCKS가 제거되고 performance_schema.data_locks / data_lock_waits가 표준입니다. 아래 조인 쿼리 하나면 "누가 누구를 막고 있는지"가 한 줄로 보입니다.
-- ① 블로킹 ↔ 대기 트랜잭션을 한 번에
SELECT
w.blocking_trx_id AS blocking_trx,
bt.trx_mysql_thread_id AS blocking_thread,
bt.trx_query AS blocking_query,
w.requesting_trx_id AS waiting_trx,
wt.trx_mysql_thread_id AS waiting_thread,
wt.trx_query AS waiting_query
FROM performance_schema.data_lock_waits w
JOIN information_schema.innodb_trx bt
ON bt.trx_id = w.blocking_trx_id
JOIN information_schema.innodb_trx wt
ON wt.trx_id = w.requesting_trx_id;-- ② 가장 오래된 active 트랜잭션 (장기 미커밋 범인 찾기)
SELECT trx_id, trx_started,
TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS run_sec,
trx_mysql_thread_id, trx_query
FROM information_schema.innodb_trx
ORDER BY trx_started ASC;-- ③ 어떤 락을 쥐고 있나
SELECT object_name, lock_type, lock_mode, lock_status, lock_data
FROM performance_schema.data_locks;그리고 데드락의 "부검 보고서"는 항상 여기에 남습니다.
SHOW ENGINE INNODB STATUS\G출력의 LATEST DETECTED DEADLOCK 블록 예시:
------------------------
LATEST DETECTED DEADLOCK
------------------------
2026-06-13 10:42:11 0x7f...
*** (1) TRANSACTION:
TRANSACTION 4211, ACTIVE 6 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136
UPDATE acct SET balance = balance + 10 WHERE id = 2
*** (1) HOLDS THE LOCK(S):
RECORD LOCKS ... index PRIMARY of table `test`.`acct` ... id=1
*** (2) TRANSACTION:
TRANSACTION 4212, ACTIVE 4 sec
UPDATE acct SET balance = balance + 10 WHERE id = 1
*** WE ROLL BACK TRANSACTION (2)HOLDS THE LOCK(S)(보유 락)와 WAITING FOR THIS LOCK(대기 락)을 양쪽 트랜잭션에서 읽으면 잠금 순서 충돌이 그대로 드러납니다. 단, SHOW ENGINE INNODB STATUS는 가장 최근 1건만 보여주므로, RDS/Aurora나 운영 서버라면 innodb_print_all_deadlocks = ON을 켜서 모든 데드락을 에러 로그에 남기는 걸 권장합니다.
원인 3패턴과 즉시 조치
실무에서 1205/1213의 90%는 아래 셋 중 하나입니다.
- 장기 미커밋 트랜잭션: 애플리케이션이
BEGIN후 외부 API 호출 등으로 커밋이 지연된 경우. 위 ②번 쿼리에서run_sec가 비정상적으로 큰 트랜잭션이 범인입니다. - 인덱스 부재로 인한 갭락·넥스트키락 확대: 인덱스 없는 컬럼을 WHERE로 걸면 풀스캔하며 광범위한 락을 잡습니다.
- 배치 잡의 잠금 순서 충돌: 두 잡이 행을 서로 반대 순서로 갱신 → 데드락.
특히 2번은 EXPLAIN으로 바로 확인됩니다.
-- 인덱스 없음: 풀스캔 → 많은 행 잠금
EXPLAIN SELECT * FROM acct WHERE balance = 100 FOR UPDATE;
-- type: ALL, rows: 전체 → 넥스트키락이 테이블 전체로 확대
ALTER TABLE acct ADD INDEX idx_balance (balance);
-- 인덱스 추가 후: type: ref, rows: 소수만 잠금
EXPLAIN SELECT * FROM acct WHERE balance = 100 FOR UPDATE;인덱스를 추가하면 잠기는 행 수가 수만 행에서 수 행으로 줄어, 락 경합 자체가 사라지는 경우가 많습니다.
즉시 조치 — KILL 대상 선정: 위 ①번 쿼리에서 blocking_thread(= trx_mysql_thread_id)를 골라 끊습니다. 선정 기준은 "가장 오래 쥐고 있으면서 다수를 막고 있는" 트랜잭션입니다.
KILL 8821; -- blocking_thread 값타임아웃 조정의 트레이드오프:
-- 세션 단위 (권장: 특정 배치만)
SET innodb_lock_wait_timeout = 5;
-- 전역 (영향 범위 큼, 신중히)
SET GLOBAL innodb_lock_wait_timeout = 20;| 설정 | 키우면 (예: 120초) | 줄이면 (예: 5초) |
|---|---|---|
| 장점 | 일시적 경합을 견딤 | 빠른 실패 → 빠른 재시도 |
| 단점 | 장애를 은폐, 대기 스레드 누적 | 정상 트랜잭션도 조기 실패 |
현장 경험 한마디: 타임아웃을 키워 급한 불을 끄고 싶은 유혹이 강하지만, 저는 반대로 짧게 줄이고 애플리케이션에 재시도 로직을 두는 쪽을 선호합니다. 타임아웃을 늘리면 그 시간만큼 대기 스레드가 쌓여 어느 순간 커넥션 풀까지 같이 무너집니다. "느린 실패"보다 "빠른 실패 + 명시적 재시도"가 운영 가시성 측면에서 훨씬 낫습니다.
참고로 innodb_deadlock_detect는 기본 ON이라 데드락을 즉시 감지하지만, 초고동시성 환경에서는 감지 비용 자체가 병목이 될 수 있습니다. 이 경우 감지를 끄고 innodb_lock_wait_timeout을 짧게 두어 타임아웃으로 처리하는 전략도 있습니다.
재발 방지 체크리스트
- 트랜잭션은 짧게:
BEGIN과COMMIT사이에 외부 API 호출·사용자 입력 대기를 절대 넣지 않기. - 인덱스로 락 범위 축소: WHERE/JOIN 조건 컬럼에 인덱스를 두어 갭락·넥스트키락 확대를 차단.
- 접근 순서 일관화: 여러 행을 갱신할 때 항상 같은 정렬(예: PK 오름차순)로 접근해 순환 대기를 원천 차단.
- 격리수준 재검토: 갭락이 부담되면
READ COMMITTED검토(단, 복제·일관성 영향 확인). - 데드락 로깅 상시화:
innodb_print_all_deadlocks = ON으로 모든 교착을 기록.
자주 묻는 질문 (FAQ)
Q. ERROR 1205가 떴는데 그냥 트랜잭션을 재시도하면 안 되나요?
A. 재시도는 임시방편일 뿐입니다. 1205는 누군가 락을 오래 쥐고 있다는 신호이므로, information_schema.innodb_trx에서 장기 미커밋 트랜잭션을 찾아 근본 원인(긴 트랜잭션·인덱스 부재)을 제거해야 재발하지 않습니다.
Q. MySQL 8.0에서 INNODB_LOCKS 테이블이 안 보입니다.
A. 8.0에서 제거되었습니다. 대신 performance_schema.data_locks와 data_lock_waits를 사용하세요. 보유 락과 대기 관계를 더 정확하게 보여줍니다.
Q. 1205와 1213을 로그만 보고 어떻게 빨리 구분하나요?
A. SQLSTATE로 구분하세요. HY000이면 락 대기(1205), 40001이면 데드락(1213)입니다. 데드락은 한쪽이 즉시 롤백되고 SHOW ENGINE INNODB STATUS의 LATEST DETECTED DEADLOCK에 기록이 남는 반면, 락 대기는 타임아웃 후 대기하던 쪽만 조용히 실패합니다.
이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.
댓글
불러오는 중...