배타 락
하나의 트랜잭션이 데이터를 수정하는 동안 다른 트랜잭션의 읽기와 쓰기를 모두 차단하는 락입니다.
공유 락과의 차이
[ 공유 락 (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)를 방지하는 핵심 도구이며, 데드락을 피하기 위해 락 순서를 일관되게 유지하고 트랜잭션을 짧게 가져가는 것이 중요합니다.