CodeGym /행동 /SQL SELF /트랜잭션 작업 시 제한사항과 잠재적 문제: LOCK, DEADLOCK

트랜잭션 작업 시 제한사항과 잠재적 문제: LOCK, DEADLOCK

SQL SELF
레벨 54 , 레슨 1
사용 가능

PostgreSQL에서 트랜잭션은 격리성을 보장해. 이건 ACID의 핵심 특성 중 하나지. 이걸 위해 시스템은 lock을 써. lock은 여러 트랜잭션이 같은 row나 table을 두고 "싸우지" 않게 해주는 메커니즘이야. 마치 마트에서 줄 서는 것처럼, 계산대에는 한 번에 한 명만 계산할 수 있잖아? lock도 비슷하게 동작해서, table의 데이터 접근을 관리해.

주요 lock 종류

PostgreSQL에는 여러 가지 lock 타입이 있어. 아마 EXPLAIN ANALYZE에서 본 적 있을 수도 있어:

  1. ROW EXCLUSIVE (행 단위 독점 lock) — row의 데이터를 바꿀 때 생겨. 제일 자주 쓰이는 lock이고, 예를 들어 INSERT, UPDATE, DELETE 할 때 걸려.
  2. SHARE (공유 lock) — 쿼리 실행 중 데이터가 안 바뀌게 보장할 때 써. 예를 들어 SELECT ... FOR SHARE 할 때.
  3. EXCLUSIVE (독점 lock) — 읽기 빼고는 다른 작업을 다 막아버려.

lock은 일종의 watchdog 같아. 우리 데이터를 지켜주는 개라고 생각하면 돼.

lock 문제: 뭐가 잘못될 수 있을까?

이제 lock이 뭔지 알았으니까, 어떤 문제가 생길 수 있는지 보자. 제일 큰 문제는 deadlock(교착 상태)이야. deadlock은 두 개(혹은 그 이상)의 트랜잭션이 서로를 기다리면서 무한 루프에 빠지는 상황이야. 보통 이런 식으로 생겨:

  1. 트랜잭션 1이 row A를 lock하고, row B에 접근하려고 해.
  2. 트랜잭션 2가 row B를 lock하고, row A에 접근하려고 해.
  3. 둘 다 lock이 걸려 있으니, 아무도 끝낼 수 없어.

deadlock 예시

deadlock에 걸린 개발자의 고통을 느끼고 싶으면, 이런 시나리오를 상상해봐:

-- 트랜잭션 1
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;

-- 동시에
-- 트랜잭션 2
BEGIN;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;

-- 트랜잭션 1이 id = 2를 lock하려고 함
UPDATE accounts SET balance = balance - 100 WHERE id = 2;

-- 트랜잭션 2가 id = 1을 lock하려고 함
UPDATE accounts SET balance = balance + 200 WHERE id = 1;

짜잔! 각 트랜잭션이 서로에게 막혀버렸어. PostgreSQL은 결국 이걸 감지하고 deadlock 에러를 던져.

deadlock을 피하려면?

  1. 데이터 접근 순서를 항상 똑같이 하자. 항상 같은 순서로 데이터를 접근하려고 해. 트랜잭션 1이 먼저 row A, 그 다음 row B를 lock한다면, 트랜잭션 2도 똑같이 row A 먼저, 그 다음 row B로 가야 해.
-- 올바른 접근 순서
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;
COMMIT;
  1. 트랜잭션을 최대한 짧게! 트랜잭션이 길어질수록 deadlock 확률이 높아져. 필요한 작업만 하고 바로 COMMIT 하자.

  2. 트랜잭션 격리 수준을 활용하자. 데이터 격리가 엄청 엄격할 필요 없다면 READ COMMITTED 같은 격리 수준을 써봐. 일부 lock을 피할 수 있어.

lock 타임아웃과 진단

PostgreSQL에는 lock을 진단하고 막는 도구들이 있어. 예를 들어, lock 최대 대기 시간을 설정할 수 있어:

SET lock_timeout = '5s';  -- lock 타임아웃을 5초로 설정

lock이 지정한 시간보다 오래 유지되면 트랜잭션이 중단돼. 완전한 교착 상태를 막아주는 거지.

PostgreSQL에서 lock 추적하기

lock 모니터링에 유용한 명령어 중 하나가 pg_locks야. 이건 시스템의 모든 활성화된 lock을 보여줘:

SELECT * FROM pg_locks;

어떤 트랜잭션이 lock을 잡고 있고, 어떤 게 해제 기다리는지 볼 수 있어. deadlock 디버깅할 때 특히 유용해.

LOCK 특징과 수동 객체 lock

lock을 직접 관리해야 할 때는 LOCK 명령어를 써:

LOCK TABLE orders IN ACCESS EXCLUSIVE MODE;

주의: ACCESS EXCLUSIVE는 제일 강력한 lock이야. SELECT조차도 막아버려. 진짜 특별한 경우(예: 테이블 구조 바꿀 때)만 써.

row 단위로 lock을 걸고 싶으면 SELECT ... FOR UPDATE를 써:

SELECT * FROM accounts WHERE id = 1 FOR UPDATE;

이렇게 하면 트랜잭션 끝날 때까지 다른 트랜잭션이 이 row를 못 바꿔.

lock에 대해 기억할 점?

lock은 나쁜 게 아니라 도구야. 데이터 무결성을 지켜주지만, 신중하게 써야 해. 몇 가지 핵심 팁을 줄게:

  1. 트랜잭션이 왜 필요한지 확실히 모르면 시작하지 마.
  2. 트랜잭션은 최대한 빨리 끝내.
  3. 데이터 접근 순서를 뒤죽박죽 하지 마.
  4. 작업에 맞는 격리 수준을 써.

이제 lock과 deadlock을 다룰 준비가 됐어! pg_locks와 ACID의 지혜가 함께하길.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION