PostgreSQL에서 트랜잭션은 격리성을 보장해. 이건 ACID의 핵심 특성 중 하나지. 이걸 위해 시스템은 lock을 써. lock은 여러 트랜잭션이 같은 row나 table을 두고 "싸우지" 않게 해주는 메커니즘이야. 마치 마트에서 줄 서는 것처럼, 계산대에는 한 번에 한 명만 계산할 수 있잖아? lock도 비슷하게 동작해서, table의 데이터 접근을 관리해.
주요 lock 종류
PostgreSQL에는 여러 가지 lock 타입이 있어. 아마 EXPLAIN ANALYZE에서 본 적 있을 수도 있어:
ROW EXCLUSIVE(행 단위 독점 lock) — row의 데이터를 바꿀 때 생겨. 제일 자주 쓰이는 lock이고, 예를 들어INSERT,UPDATE,DELETE할 때 걸려.SHARE(공유 lock) — 쿼리 실행 중 데이터가 안 바뀌게 보장할 때 써. 예를 들어SELECT ... FOR SHARE할 때.EXCLUSIVE(독점 lock) — 읽기 빼고는 다른 작업을 다 막아버려.
lock은 일종의 watchdog 같아. 우리 데이터를 지켜주는 개라고 생각하면 돼.
lock 문제: 뭐가 잘못될 수 있을까?
이제 lock이 뭔지 알았으니까, 어떤 문제가 생길 수 있는지 보자. 제일 큰 문제는 deadlock(교착 상태)이야. deadlock은 두 개(혹은 그 이상)의 트랜잭션이 서로를 기다리면서 무한 루프에 빠지는 상황이야. 보통 이런 식으로 생겨:
- 트랜잭션 1이 row A를 lock하고, row B에 접근하려고 해.
- 트랜잭션 2가 row B를 lock하고, row A에 접근하려고 해.
- 둘 다 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이 먼저 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;
트랜잭션을 최대한 짧게! 트랜잭션이 길어질수록 deadlock 확률이 높아져. 필요한 작업만 하고 바로
COMMIT하자.트랜잭션 격리 수준을 활용하자. 데이터 격리가 엄청 엄격할 필요 없다면
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은 나쁜 게 아니라 도구야. 데이터 무결성을 지켜주지만, 신중하게 써야 해. 몇 가지 핵심 팁을 줄게:
- 트랜잭션이 왜 필요한지 확실히 모르면 시작하지 마.
- 트랜잭션은 최대한 빨리 끝내.
- 데이터 접근 순서를 뒤죽박죽 하지 마.
- 작업에 맞는 격리 수준을 써.
이제 lock과 deadlock을 다룰 준비가 됐어! pg_locks와 ACID의 지혜가 함께하길.
GO TO FULL VERSION