배타 락

하나의 트랜잭션이 데이터를 수정하는 동안 다른 트랜잭션의 읽기와 쓰기를 모두 차단하는 락입니다.

공유 락과의 차이

[ 공유 락 (Shared Lock) ]
"나 읽는 중이야, 너희도 읽어도 돼. 근데 수정은 안 돼"
→ 읽기 + 읽기 = 동시 가능

[ 배타 락 (Exclusive Lock) ]
"나 수정 중이야, 아무도 건드리지 마"
→ 읽기도 X, 쓰기도 X
→ 완전 독점

락 호환성

             요청하는 락
              S     X
          ┌──────┬──────┐
보유 중  S │  O   │  X   │
인 락   X │  X   │  X   │
          └──────┴──────┘

배타 락(X)을 보유하면:
- 다른 트랜잭션의 공유 락 요청 → 대기
- 다른 트랜잭션의 배타 락 요청 → 대기
→ 완전한 독점 상태
상황결과
배타 락 + 공유 락 요청X 대기
배타 락 + 배타 락 요청X 대기
공유 락 + 배타 락 요청X 대기
공유 락 + 공유 락 요청O 허용

동작 시나리오

시나리오 1: 쓰기 중 읽기 시도 → 대기

시간 →→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→

트랜잭션 A: [X락 획득]────쓰기────[X락 해제]
트랜잭션 B:     [S락 요청]──대기──대기──[S락 획득]──읽기──
                    ↑                     ↑
            X락이 있어서 읽기도 대기    X락 해제 후 읽기 가능

시나리오 2: 쓰기 중 쓰기 시도 → 대기

시간 →→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→

트랜잭션 A: [X락 획득]────쓰기────[X락 해제]
트랜잭션 B:     [X락 요청]──대기──대기──[X락 획득]──쓰기──
                    ↑                     ↑
            X락끼리 충돌, 대기         순차적으로 처리

시나리오 3: 읽기 중 쓰기 시도 → 대기

시간 →→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→

트랜잭션 A: [S락 획득]────읽기────[S락 해제]
트랜잭션 B: [S락 획득]────읽기────[S락 해제]
트랜잭션 C:     [X락 요청]──대기──대기──대기──[X락 획득]──쓰기
                    ↑                            ↑
            여러 S락이 있어서 대기         모든 S락 해제 후 X락 획득

SQL에서 배타 락 사용

명시적 배타 락

-- MySQL / PostgreSQL
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;

-- 해당 행에 배타 락이 걸림
-- 다른 트랜잭션의 SELECT FOR UPDATE, SELECT FOR SHARE, UPDATE, DELETE 모두 대기

암시적 배타 락 (자동)

-- INSERT, UPDATE, DELETE는 자동으로 배타 락 획득
UPDATE accounts SET balance = 500 WHERE id = 1;
-- id = 1인 행에 자동으로 X락

DELETE FROM accounts WHERE id = 1;
-- id = 1인 행에 자동으로 X락

INSERT INTO accounts (id, balance) VALUES (2, 1000);
-- 새 행에 자동으로 X락

실제 사용 예시

계좌 이체 — 잔액 확인 후 수정

-- 트랜잭션 A: 출금
BEGIN;

-- 배타 락으로 잔액 조회 (수정할 예정이므로)
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
-- balance = 1000, X락 획득

-- 잔액 확인 후 출금
UPDATE accounts SET balance = balance - 500 WHERE id = 1;

COMMIT;  -- X락 해제
-- 트랜잭션 B: 동시에 같은 계좌 출금 시도
BEGIN;

SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
-- ↑ 트랜잭션 A가 X락을 보유 중이므로 대기
-- 트랜잭션 A가 COMMIT하면 이어서 실행

-- 최신 잔액(500)으로 처리됨
UPDATE accounts SET balance = balance - 300 WHERE id = 1;

COMMIT;
배타 락이 없으면:

트랜잭션 A: 잔액 조회 (1000)
트랜잭션 B: 잔액 조회 (1000)    ← 같은 값을 봄
트랜잭션 A: 1000 - 500 = 500 저장
트랜잭션 B: 1000 - 300 = 700 저장   ← 덮어씀!

결과: 800이 되어야 하는데 700
→ Lost Update 문제

배타 락 사용하면:

트랜잭션 A: X락 획득, 잔액 조회 (1000)
트랜잭션 B: X락 요청 → 대기
트랜잭션 A: 1000 - 500 = 500 저장, COMMIT
트랜잭션 B: X락 획득, 잔액 조회 (500)  ← 최신 값
트랜잭션 B: 500 - 300 = 200 저장, COMMIT

결과: 200 (정확!)

재고 차감 — 동시 주문 처리

-- 주문 처리 트랜잭션
BEGIN;

-- 재고 확인 + 배타 락 (동시 주문 방지)
SELECT stock FROM products WHERE id = 100 FOR UPDATE;
-- stock = 5, X락 획득

-- 재고가 충분하면 차감
UPDATE products SET stock = stock - 1 WHERE id = 100;

-- 주문 생성
INSERT INTO orders (product_id, quantity) VALUES (100, 1);

COMMIT;

락 대기 옵션

NOWAIT — 락 획득 실패 시 즉시 에러

-- 대기하지 않고 즉시 실패
SELECT * FROM accounts WHERE id = 1 FOR UPDATE NOWAIT;

-- 다른 트랜잭션이 락을 보유 중이면
-- ERROR: could not obtain lock on row

SKIP LOCKED — 락 걸린 행 건너뛰기

-- 락 걸린 행은 제외하고 결과 반환
SELECT * FROM jobs WHERE status = 'pending'
FOR UPDATE SKIP LOCKED
LIMIT 1;

-- 작업 큐 처리에 유용
-- 여러 워커가 동시에 다른 작업을 가져갈 수 있음

타임아웃 설정

-- MySQL: 락 대기 타임아웃 (초)
SET innodb_lock_wait_timeout = 5;

-- PostgreSQL: 락 대기 타임아웃 (밀리초)
SET lock_timeout = '5s';

공유 락 vs 배타 락 사용 판단

데이터를 수정할 예정인가?
    │
    ├── Yes → 배타 락 (FOR UPDATE)
    │         "수정할 거니까 아무도 건드리지 마"
    │
    └── No ─→ 읽기만 하는가?
               │
               ├── 읽는 동안 변경되면 안 됨 → 공유 락 (FOR SHARE)
               │   "읽는 동안 수정하지 마, 다른 읽기는 OK"
               │
               └── 변경되어도 상관없음 → 락 없이 일반 SELECT
                   "그냥 현재 값만 보면 돼"
상황사용할 락
잔액 확인 후 출금배타 락 (FOR UPDATE)
재고 확인 후 차감배타 락 (FOR UPDATE)
보고서용 데이터 조회공유 락 (FOR SHARE) 또는 일반 SELECT
단순 화면 표시일반 SELECT (락 없음)
작업 큐에서 작업 가져오기배타 락 + SKIP LOCKED

데드락 주의

-- 트랜잭션 A
BEGIN;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;  -- 행 1에 X락
-- ...
SELECT * FROM accounts WHERE id = 2 FOR UPDATE;  -- 행 2에 X락 요청 → 대기

-- 트랜잭션 B (동시에)
BEGIN;
SELECT * FROM accounts WHERE id = 2 FOR UPDATE;  -- 행 2에 X락
-- ...
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;  -- 행 1에 X락 요청 → 대기

-- 서로 대기 → DEADLOCK!
예방법:
1. 항상 같은 순서로 락 획득 (id 오름차순 등)
2. 트랜잭션을 짧게 유지
3. 락 타임아웃 설정
4. NOWAIT / SKIP LOCKED 활용

핵심 정리

배타 락은 데이터 수정 시 다른 트랜잭션의 읽기와 쓰기를 모두 차단하는 완전 독점 락입니다. SELECT ... FOR UPDATE로 명시적으로 획득하거나, UPDATE/DELETE/INSERT 시 자동으로 걸립니다. 공유 락이 "읽는 동안 수정 방지"라면, 배타 락은 "수정하는 동안 모든 접근 방지"입니다. 계좌 이체, 재고 차감 등 데이터를 읽고 수정하는 작업에서 동시성 문제(Lost Update)를 방지하는 핵심 도구이며, 데드락을 피하기 위해 락 순서를 일관되게 유지하고 트랜잭션을 짧게 가져가는 것이 중요합니다.

CSLOG에서 보기