명령어/DB

[PostgreSQL] 데드락 두 트랜잭션이 서로의 잠금을 기다리다 하나가 강제 중단될 때

jykim23 2026. 7. 23. 21:53
반응형

설치·접속: PostgreSQL 설치와 접속

부제: 계좌이체 두 건이 각각 다른 순서로 두 행을 잠그다가, 서로가 상대의 락을 기다려 영원히 못 끝나는 상황

-- 세션 A: 계좌1 → 계좌2 순서로 잠근다
BEGIN;
UPDATE m7_acct SET balance=balance-10 WHERE id=1;  -- 계좌1 락
UPDATE m7_acct SET balance=balance+10 WHERE id=2;  -- 계좌2 락 시도 (B가 쥐고 있음)
COMMIT;

-- 세션 B: 계좌2 → 계좌1 순서로 잠근다 (반대!)
BEGIN;
UPDATE m7_acct SET balance=balance-10 WHERE id=2;  -- 계좌2 락
UPDATE m7_acct SET balance=balance+10 WHERE id=1;  -- 계좌1 락 시도 (A가 쥐고 있음)
COMMIT;
[A] A: 계좌1 잠금 완료
[B] B: 계좌2 잠금 완료
[A] A: 이제 계좌2 잠그려 시도...
[B] B: 이제 계좌1 잠그려 시도...
[A] ERROR:  deadlock detected
[A] DETAIL:  Process 9900 waits for ShareLock on transaction 929; blocked by process 9901.
[A]         Process 9901 waits for ShareLock on transaction 930; blocked by process 9900.
[A] CONTEXT:  while updating tuple (0,2) in relation "m7_acct"
[B] B: 계좌1도 잠금 (성공)   <- B는 살아남아 정상 진행

A는 계좌1을 쥔 채 계좌2를 기다리고, B는 계좌2를 쥔 채 계좌1을 기다린다. 서로가 상대의 락을 기다리므로 그냥 두면 둘 다 영원히 멈춘다. PostgreSQL은 deadlock_timeout(기본 1초)마다 대기 그래프에 순환이 생겼는지 검사하고, 순환을 발견하면 한쪽을 희생자로 골라 40P01 에러로 강제 중단한다. 여기선 A가 죽고 B가 살아 진행했다. 데드락은 락 획득 순서가 트랜잭션마다 엇갈릴 때 생기므로, 근본 해결책은 "항상 id 오름차순으로 잠근다"처럼 잠금 순서를 통일하는 것이다. 그리고 앱은 40P01을 잡으면 트랜잭션을 재시도해야 한다.

이렇게도 쓴다

락 순서를 통일해 데드락 자체를 없앤다(양쪽 다 id 오름차순).

-- 어느 트랜잭션이든 항상 작은 id부터 잠근다
UPDATE m7_acct SET balance=balance-10 WHERE id=LEAST(1,2);
UPDATE m7_acct SET balance=balance+10 WHERE id=GREATEST(1,2);

 

한 문장으로 두 행을 함께 잠근다(순서 뒤섞임 방지). (조합: IN + ORDER BY)

SELECT id FROM m7_acct WHERE id IN (1,2) ORDER BY id FOR UPDATE;

 

데드락 감지 주기를 조정한다. (조합: deadlock_timeout)

SET deadlock_timeout = '2s';   -- 짧은 락 대기를 데드락으로 오판하지 않게

 

앱에서 데드락만 골라 재시도한다. (조합: SQLSTATE 40P01)

-- SQLSTATE '40P01'(deadlock_detected) 잡으면 트랜잭션 재실행

 

지금 서로 기다리며 막혀 있는 세션을 찾아낸다. (조합: pg_blocking_pids)

SELECT pid, pg_blocking_pids(pid) AS blocked_by, query
FROM pg_stat_activity WHERE cardinality(pg_blocking_pids(pid)) > 0;

 

데드락 로그를 서버 로그에 남겨 사후 분석한다. (조합: log_lock_waits)

ALTER SYSTEM SET log_lock_waits = on;   -- 대기·데드락 상세를 로그로
SELECT pg_reload_conf();
반응형