홈/개발/MySQL ERROR 1205 Lock wait timeout 진단: 락 대기 vs 데드락 구분
개발MySQL ERROR 1205InnoDB 락 경합

MySQL ERROR 1205 Lock wait timeout 진단: 락 대기 vs 데드락 구분

MySQL ERROR 1205(Lock wait timeout)와 1213(데드락)을 SQLSTATE로 구분하고, performance_schema.data_locks·INNODB_TRX·SHOW ENGINE INNODB STATUS로 블로킹 트랜잭션을 5분 안에 찾는 복붙 SQL을 정리했습니다.

MySQL ERROR 1205 Lock wait timeout 진단: 락 대기 vs 데드락 구분
목차
  1. 1205와 1213은 전혀 다른 문제다
  2. 직접 재현해 보기 (Session A / B)
  3. 5분 진단: 블로킹 트랜잭션 추적 3종 세트
  4. 원인 3패턴과 즉시 조치
  5. 재발 방지 체크리스트
  6. 자주 묻는 질문 (FAQ)

MySQL ERROR 1205 Lock wait timeout 실전 진단: 락 대기 vs 데드락 구분법

운영 중인 서비스에서 갑자기 트랜잭션이 멈추고 다음 메시지가 떴다면, 이 글은 정확히 당신을 위한 것입니다.

CODE
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

메시지 끝에 try restarting transaction이 붙어 있다고 해서 진짜로 재시도만 반복하면, 근본 원인은 그대로 남아 같은 장애가 끝없이 재발합니다. 그리고 한 가지 더 — 예전에 다룬 'PostgreSQL too many clients'(커넥션 풀 고갈, 즉 연결 자원이 부족한 문제)와 이 글은 각도가 완전히 다릅니다. 여기서는 연결은 멀쩡히 살아있는데 트랜잭션들이 서로의 락을 기다리느라 멈춰버린 상황을 다룹니다.

1205와 1213은 전혀 다른 문제다

가장 먼저 할 일은 두 에러 문자열을 나란히 놓고 구분하는 것입니다.

CODE
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 (데드락)
SQLSTATEHY00040001
발생 메커니즘innodb_lock_wait_timeout(기본 50초)이 지나도록 락을 못 얻음InnoDB가 순환 대기를 즉시 감지
처리 방식대기하던 한 트랜잭션만 타임아웃 실패InnoDB가 비용이 적은 한쪽을 victim으로 즉시 롤백
발생까지 시간타임아웃(수십 초)을 기다림즉시(밀리초)
핵심 신호"느리게 실패한다""한쪽이 바로 죽는다"

핵심은 이렇습니다. 1205는 누군가 락을 너무 오래 잡고 안 놔줘서 발생하는 "단방향 대기"이고, 1213은 두 트랜잭션이 서로의 락을 마주 잡아 순환이 생긴 "양방향 교착"입니다. 1205가 떴다면 "누가 락을 오래 쥐고 있나"를 추적하고, 1213이 떴다면 "어떤 두 쿼리가 잠금 순서를 충돌시켰나"를 봐야 합니다.

직접 재현해 보기 (Session A / B)

개념을 머리로만 이해하면 현장에서 헷갈립니다. 두 세션을 열어 직접 만들어 봅시다.

SQL
-- 준비
CREATE TABLE acct (id INT PRIMARY KEY, balance INT);
INSERT INTO acct VALUES (1,100),(2,100);

(A) 락 대기 → 1205 재현

SQL
-- 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 재현

SQL
-- 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가 표준입니다. 아래 조인 쿼리 하나면 "누가 누구를 막고 있는지"가 한 줄로 보입니다.

SQL
-- ① 블로킹 ↔ 대기 트랜잭션을 한 번에
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;
SQL
-- ② 가장 오래된 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;
SQL
-- ③ 어떤 락을 쥐고 있나
SELECT object_name, lock_type, lock_mode, lock_status, lock_data
FROM performance_schema.data_locks;

그리고 데드락의 "부검 보고서"는 항상 여기에 남습니다.

SQL
SHOW ENGINE INNODB STATUS\G

출력의 LATEST DETECTED DEADLOCK 블록 예시:

CODE
------------------------
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%는 아래 셋 중 하나입니다.

  1. 장기 미커밋 트랜잭션: 애플리케이션이 BEGIN 후 외부 API 호출 등으로 커밋이 지연된 경우. 위 ②번 쿼리에서 run_sec가 비정상적으로 큰 트랜잭션이 범인입니다.
  2. 인덱스 부재로 인한 갭락·넥스트키락 확대: 인덱스 없는 컬럼을 WHERE로 걸면 풀스캔하며 광범위한 락을 잡습니다.
  3. 배치 잡의 잠금 순서 충돌: 두 잡이 행을 서로 반대 순서로 갱신 → 데드락.

특히 2번은 EXPLAIN으로 바로 확인됩니다.

SQL
-- 인덱스 없음: 풀스캔 → 많은 행 잠금
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)를 골라 끊습니다. 선정 기준은 "가장 오래 쥐고 있으면서 다수를 막고 있는" 트랜잭션입니다.

SQL
KILL 8821;   -- blocking_thread 값

타임아웃 조정의 트레이드오프:

SQL
-- 세션 단위 (권장: 특정 배치만)
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에 기록이 남는 반면, 락 대기는 타임아웃 후 대기하던 쪽만 조용히 실패합니다.

✦ ✦ ✦
출처·검증 안내

글마다 출처와 확인 범위가 다릅니다. 아래에 확인일·확인 범위·출처가 기록된 글만 그 범위에서 확인된 것입니다. 기록이 없는 글은 적용 전 사용 중인 버전의 공식 문서로 직접 확인하고, 오류는 이메일로 알려주세요. 편집 정책 · 이메일로 제보

이 글에는 사람이 확인한 날짜·범위 기록이 아직 없습니다.

발행 ·
주제 관련 공식 문서(자동 연결 · 문장별 근거 아님)MySQL 공식 매뉴얼 ↗

댓글

첫 번째 댓글을 남겨보세요.