IT이야기/데이터베이스

데드락(Deadlock): SQL에서 교착 상태를 방지하는 최적의 해결책

Chiba-in 2025. 2. 27. 14:30

🔹 데드락(Deadlock)이란?

1. 데드락(Deadlock)의 정의

데드락(Deadlock)두 개 이상의 트랜잭션이 서로 상대방의 리소스(예: 데이터 행, 테이블 락)를 기다리며 영원히 진행되지 않는 상태를 의미합니다.

데드락이 발생하는 주요 원인:

  • 트랜잭션 간 자원(락, Lock) 경쟁
  • 락 순서가 꼬여 발생하는 상호 대기 상황
  • 긴 트랜잭션 실행으로 인해 락 점유 시간 증가
  • 동시성 처리를 고려하지 않은 쿼리 설계

📌 데드락이 발생하면 해당 트랜잭션은 무한 대기 상태에 빠지며, 데이터베이스 성능이 저하됨


🔹 데드락의 동작 방식

1. 데드락 발생 예제

✔️ Step 1: 두 개의 트랜잭션이 실행됨

-- 트랜잭션 1: A → B 순서로 락 획득
BEGIN TRANSACTION;
UPDATE Accounts SET Balance = Balance - 100 WHERE AccountID = 1;  -- A 테이블 락
WAITFOR DELAY '00:00:05';  -- 5초 대기
UPDATE Accounts SET Balance = Balance + 100 WHERE AccountID = 2;  -- B 테이블 락
COMMIT;
-- 트랜잭션 2: B → A 순서로 락 획득
BEGIN TRANSACTION;
UPDATE Accounts SET Balance = Balance - 100 WHERE AccountID = 2;  -- B 테이블 락
WAITFOR DELAY '00:00:05';  -- 5초 대기
UPDATE Accounts SET Balance = Balance + 100 WHERE AccountID = 1;  -- A 테이블 락
COMMIT;

📌 두 트랜잭션이 서로가 점유한 리소스를 기다리며 무한 대기 상태(Deadlock)에 빠짐


2. 데드락 감지 (Deadlock Detection)

대부분의 데이터베이스는 데드락을 감지하면 자동으로 하나의 트랜잭션을 강제 종료(Rollback) 합니다.

✔️ MySQL에서 데드락 감지 예제

SHOW ENGINE INNODB STATUS;

📌 현재 발생한 데드락과 관련된 정보를 확인 가능

✔️ SQL Server에서 데드락 감지 로그 확인

SELECT * FROM sys.dm_tran_locks;

📌 현재 트랜잭션의 락 상태를 모니터링 가능


🔹 데드락 방지 기법

1. 트랜잭션 실행 순서 조정

모든 트랜잭션이 동일한 순서로 자원을 잠그도록 설계
✔️ 해결 방법 예제 (A → B 순서로 고정)

BEGIN TRANSACTION;
UPDATE Accounts SET Balance = Balance - 100 WHERE AccountID = 1;  -- 항상 A부터 락 획득
UPDATE Accounts SET Balance = Balance + 100 WHERE AccountID = 2;
COMMIT;

📌 모든 트랜잭션이 동일한 순서로 락을 요청하면 데드락 발생 확률이 감소


2. 락 범위 최소화 (Lock Granularity Reduction)

테이블 락(Table Lock) 대신 행 락(Row Lock) 사용
✔️ MySQL에서 행 수준 락 사용 예제

SELECT * FROM Accounts WHERE AccountID = 1 FOR UPDATE;

📌 불필요한 테이블 락을 방지하여 병렬 실행 성능 향상


3. 트랜잭션 타임아웃 설정

지정된 시간 안에 락이 해제되지 않으면 트랜잭션 강제 종료
✔️ MySQL에서 락 대기 시간 설정

SET innodb_lock_wait_timeout = 5;

✔️ SQL Server에서 락 타임아웃 설정

SET LOCK_TIMEOUT 5000;  -- 5초 후 강제 종료

📌 데드락이 발생해도 일정 시간 후 자동으로 해결됨


4. 인덱스 최적화로 락 충돌 방지

인덱스를 활용하여 필요한 데이터만 락을 획득
✔️ 비효율적인 인덱스 사용 예제

UPDATE Orders SET Status = 'Shipped' WHERE CustomerID = 10;

📌 CustomerID에 인덱스가 없으면 전체 테이블이 락에 걸릴 수 있음

✔️ 인덱스를 활용한 최적화 예제

CREATE INDEX idx_orders_customer ON Orders (CustomerID);
UPDATE Orders SET Status = 'Shipped' WHERE CustomerID = 10;

📌 인덱스를 사용하여 특정 행만 업데이트하면 데드락 발생 확률이 감소


5. 재시도 로직 적용 (Retry Mechanism)

트랜잭션이 데드락으로 인해 롤백되면 일정 횟수까지 재시도
✔️ Python + MySQL에서 재시도 로직 구현 예제

import mysql.connector
import time

def execute_transaction():
    for attempt in range(3):  # 최대 3회 재시도
        try:
            conn = mysql.connector.connect(user='user', password='pass', database='testdb')
            cursor = conn.cursor()
            cursor.execute("START TRANSACTION")
            cursor.execute("UPDATE Accounts SET Balance = Balance - 100 WHERE AccountID = 1")
            cursor.execute("UPDATE Accounts SET Balance = Balance + 100 WHERE AccountID = 2")
            conn.commit()
            break
        except mysql.connector.Error as err:
            conn.rollback()
            print(f"데드락 발생, 재시도 {attempt + 1}/3...")
            time.sleep(2)  # 2초 대기 후 재시도
        finally:
            cursor.close()
            conn.close()

execute_transaction()

📌 데드락이 발생하면 트랜잭션을 롤백하고 일정 횟수까지 자동으로 재시도


📌 결론

데드락(Deadlock)은 SQL에서 발생하는 대표적인 교착 상태 문제로, 트랜잭션이 서로의 리소스를 기다리며 무한 대기에 빠지는 현상이다.
트랜잭션 실행 순서를 조정하고, 락 범위를 최소화하면 데드락 발생 확률을 줄일 수 있다.
트랜잭션 타임아웃 설정 및 인덱스 최적화를 통해 불필요한 락 충돌을 방지할 수 있다.
재시도 로직을 적용하면 데드락이 발생해도 자동으로 해결할 수 있다.