본문 바로가기
Database & Storage

트랜잭션 잠금(Lock) 완전 정복: Deadlock 재현·원인 분석·해결 전략 실무 가이드

by yamoojin83 2025. 12. 3.

트랜잭션 잠금(Lock) 완전 정복: Deadlock 재현·원인 분석·해결 전략 실무 가이드


트랜잭션 Lock은 데이터베이스 동시성 처리에서 가장 중요한 요소 중 하나입니다. 하지만 대부분의 초급·중급 개발자는 코드를 잘 작성하고도 “왜 Deadlock이 발생했는지” 이해하지 못해 디버깅에 많은 시간을 허비합니다.

특히 실무에서는 다음과 같은 상황이 매우 자주 발생합니다.

- 두 개의 API가 동시에 같은 row를 업데이트하면서 서로 Lock을 기다림 → Deadlock
- UPDATE 쿼리가 빠른데 SELECT가 느린 이유 → MVCC와 Lock 경합
- Isolation Level을 높였더니 성능이 급격히 하락
- 트랜잭션이 늘어날수록 API 지연시간이 증가

이 글은 Spring Boot + JPA + PostgreSQL(MySQL 도 적용 가능) 기준으로, Deadlock이 왜 발생하는지, 어떻게 재현하고 해결하는지를 실전 중심으로 정리한 가이드입니다.

Deadlock 개념도

1. Lock은 왜 존재할까? (DB 동시성 제어 기본)


트랜잭션이 여러 개 동시에 같은 row를 읽고·수정하고·삭제할 때, 데이터 정합성을 유지하기 위해 DB는 자동으로 Lock을 겁니다.

DB Lock의 핵심 목적은 다음과 같습니다.

■ Dirty Read 방지 ■ Lost Update 방지 ■ Phantom Read 방지 ■ 데이터 무결성 유지

DB는 상황에 따라 다양한 Lock을 사용합니다.

- Shared Lock (읽기 잠금: SELECT FOR SHARE) - Exclusive Lock (쓰기 잠금: UPDATE/DELETE) - Row Lock - Tuple Lock (PostgreSQL) - Gap Lock (MySQL InnoDB) - Table Lock - Intent Lock

이 Lock들이 서로 경합하거나 순서가 바뀌면 Deadlock으로 이어집니다.

2. Deadlock은 왜 발생하는가? (실무에서 가장 흔한 예시)


Deadlock은 간단하게 말하면 다음 상황입니다.

A 트랜잭션 → row1을 Lock 잡음 B 트랜잭션 → row2를 Lock 잡음 그런데

A가 row2를 기다리고 B가 row1을 기다리면 서로 영원히 끝나지 않는 교착 상태가 됩니다.

DB는 이를 감지하면 다음과 같은 에러를 발생시키며 한쪽 트랜잭션을 강제로 종료합니다.

PostgreSQL:
ERROR: deadlock detected

MySQL:
ERROR 1213 (40001): Deadlock found when trying to get lock

중요한 점은… Deadlock은 DB 장애가 아니라 개발자 설계 문제라는 것입니다.

3. Deadlock을 쉽게 재현해보자 (실무 테스트용 코드)


가장 흔한 Deadlock 예시는 “두 트랜잭션이 서로 다른 순서로 row를 업데이트하는 경우”입니다.

트랜잭션 1


BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;

트랜잭션 2


BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 2;
UPDATE account SET balance = balance + 100 WHERE id = 1;


즉,
트랜잭션1: 1 → 2 순서로 Lock 잡음 트랜잭션2: 2 → 1 순서로 Lock 잡음

순서가 다르기 때문에 Deadlock이 발생합니다.

Row Lock & Transaction Flow

4. DB 별 Lock 차이 (PostgreSQL vs MySQL)


DB마다 Lock 전략이 조금 다릅니다.

PostgreSQL

- MVCC 기반 - Tuple Lock - Gap Lock 없음 - Deadlock 감지가 빠르고 과감

MySQL InnoDB

- Row Lock + Gap Lock - Phantom Read 방지를 위해 더 aggressive - Insert 시 gap lock 발생 가능

즉, **동일한 JPA 코드도 DB 종류에 따라 Lock 패턴이 완전히 달라질 수 있습니다.**

5. Spring Boot에서 Lock 모드 사용하기


JPA는 다음과 같은 LockMode를 제공합니다.

■ PESSIMISTIC_WRITE (쓰기 잠금)
■ PESSIMISTIC_READ (읽기 잠금)
■ PESSIMISTIC_FORCE_INCREMENT (버전 증가)

예시:


@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select a from Account a where a.id = :id")
Account findForUpdate(Long id);


이 코드는 SQL로 변환되면 다음과 같습니다.

SELECT ... FOR UPDATE

즉, row-level exclusive lock을 잡습니다.

6. Deadlock을 피하는 가장 절대적인 규칙 4가지


Deadlock 문제는 90%가 다음 규칙 미준수에서 발생합니다.

규칙 1) 항상 동일한 순서로 row 업데이트
→ id ASC 기준으로 항상 정렬된 순서로 update → 순서만 통일해도 Deadlock 70% 감소

규칙 2) 트랜잭션을 짧게 유지
→ DB 커넥션 점유 시간이 길어지면 Lock이 쉽게 경합

규칙 3) SELECT FOR UPDATE 사용 시 필요한 컬럼만 가져오기
→ 불필요한 row lock 방지

규칙 4) 하나의 API에서 여러 리소스를 동시에 lock하지 않기
→ 작업 단위를 쪼개야 deadlock 위험이 줄어듦

7. Deadlock 분석: PostgreSQL에서 현재 Lock 조회


Deadlock이 발생하면 가장 먼저 확인해야 하는 SQL은 아래입니다.


SELECT * FROM pg_locks pl
JOIN pg_stat_activity psa
ON pl.pid = psa.pid;


그리고 어떤 row에서 경합이 발생했는지 확인하려면:


SELECT 
    locktype, relation::regclass, page, tuple, pid, mode, granted
FROM pg_locks
WHERE NOT granted;


여기서 특정 relation(테이블), tuple(row)을 기준으로 Lock이 어디에서 발생했는지 파악할 수 있습니다.

8. MySQL Deadlock 분석: SHOW ENGINE INNODB STATUS


MySQL Deadlock 분석의 핵심 명령어는 다음 하나입니다.


SHOW ENGINE INNODB STATUS;


이 명령은 Deadlock 발생 시점의 쿼리를 포함한 전체 상황을 보여줍니다. 특히 다음 영역을 잘 봐야 합니다.

- WAITING FOR LOCK 부분 - HOLDS THE LOCK(S) 부분 - TRANSACTION 번호 - INDEX 이름 - ROW LOCK STRUCTURE

Lock Monitoring Visualization

9. 실무 Deadlock 해결 전략 6가지


Deadlock은 대부분 단순한 설계 실수에서 발생합니다. 아래 6가지 전략을 사용하면 Deadlock 확률을 거의 90% 이상 줄일 수 있습니다.

전략 1) row 접근 순서 통일
→ id 기준 오름차순 업데이트

전략 2) 재시도 로직 추가
→ Deadlock은 항상 한쪽만 rollback 되므로 retry 1~2회면 충분

전략 3) 트랜잭션 경량화
→ 하나의 API에서 여러 UPDATE를 처리하지 말고 분리

전략 4) 적절한 Isolation Level 사용
→ SERIALIZABLE은 거의 쓰지 말 것 → READ COMMITTED + FOR UPDATE 조합이 가장 안전

전략 5) 인덱스 정리
→ 인덱스가 잘못되면 불필요한 row에 Lock이 걸림

전략 6) 메시지 큐 사용
→ 동시에 동일 자원을 갱신하는 작업은 MQ로 직렬화하는 것이 안정적

10. 결론: Deadlock은 피할 수 없는 문제지만 해결 가능한 문제다


Deadlock은 장애가 아니라 **동시성 설계 미숙으로 발생하는 자연스러운 현상**입니다. 중요한 것은 Deadlock을 “무서워하지 않고, 재현하고, 분석하고, 해결할 수 있는 능력”을 갖추는 것입니다.

실무에서 Deadlock을 잘 다루는 개발자는 다음과 같은 장점을 갖습니다.

■ 동시성 문제를 빠르게 해결
■ API 지연의 원인을 정확하게 분석
■ 성능 최적화에서 강력한 역량을 발휘

이 글을 통해 Deadlock을 정확히 이해하고, 당신의 Spring Boot 애플리케이션에서 안정적인 트랜잭션 설계를 구축해보시기 바랍니다.