공유 락

여러 트랜잭션이 동시에 읽기 작업을 수행할 수 있도록 허용하되, 쓰기 작업은 차단하는 락입니다.

왜 필요한가

[ 락이 없으면 ]
트랜잭션 A: 데이터 읽는 중 (잔액: 1000원)
트랜잭션 B: 데이터 수정 (잔액: 1000원 → 500원)
트랜잭션 A: 읽기 완료 (잔액: 1000원... 인 줄 알았는데 이미 500원)
→ 데이터 불일치 (Dirty Read)

[ 공유 락 사용 ]
트랜잭션 A: 공유 락 획득 → 데이터 읽기 중
트랜잭션 B: 수정하려고 배타 락 요청 → 대기
트랜잭션 A: 읽기 완료 → 공유 락 해제
트랜잭션 B: 이제 배타 락 획득 → 수정
→ 데이터 일관성 보장

공유 락 vs [[배타 락

┌────────────────────────────────────────────────────────┐
│              공유 락 (Shared Lock, S Lock)              │
│              = Read Lock                                │
│                                                         │
│  "나 읽는 중이야, 너희도 읽어도 돼. 근데 수정은 안 돼"     │
└────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────┐
│              배타 락 (Exclusive Lock, X Lock)           │
│              = Write Lock                               │
│                                                         │
│  "나 수정 중이야, 아무도 읽거나 쓰면 안 돼"               │
└────────────────────────────────────────────────────────┘
항목공유 락 (S)배타 락 (X)
용도읽기 (SELECT)쓰기 (INSERT, UPDATE, DELETE)
동시 공유 락O 허용X 불허
동시 배타 락X 불허X 불허
다른 이름Read LockWrite Lock

락 호환성 매트릭스

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

O = 호환 (동시 보유 가능)
X = 충돌 (대기해야 함)
공유 락 + 공유 락 = O 동시 가능 (여러 트랜잭션이 같이 읽기)
공유 락 + 배타 락 = X 대기 (읽는 중에 쓰기 불가)
배타 락 + 공유 락 = X 대기 (쓰는 중에 읽기 불가)
배타 락 + 배타 락 = X 대기 (쓰는 중에 쓰기 불가)

동작 시나리오

시나리오 1: 읽기 + 읽기 = 동시 가능

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

트랜잭션 A: [S락 획득]───읽기───[S락 해제]
트랜잭션 B:     [S락 획득]────읽기────[S락 해제]
트랜잭션 C:         [S락 획득]──읽기──[S락 해제]
                    ↑
              동시에 공유 락 보유 가능

시나리오 2: 읽기 + 쓰기 = 대기

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

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

시나리오 3: 쓰기 + 읽기 = 대기

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

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

SQL에서 락 사용

MySQL / InnoDB

-- 공유 락 (읽기 락)
SELECT * FROM accounts WHERE id = 1 FOR SHARE;
-- 또는 (구버전 문법)
SELECT * FROM accounts WHERE id = 1 LOCK IN SHARE MODE;

-- 배타 락 (쓰기 락)
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;

-- 일반 SELECT는 락을 걸지 않음 (MVCC로 처리)
SELECT * FROM accounts WHERE id = 1;

PostgreSQL

-- 공유 락
SELECT * FROM accounts WHERE id = 1 FOR SHARE;

-- 배타 락
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;

-- 락 대기 없이 즉시 실패
SELECT * FROM accounts WHERE id = 1 FOR UPDATE NOWAIT;

-- 락 걸린 행 건너뛰기
SELECT * FROM accounts WHERE id = 1 FOR UPDATE SKIP LOCKED;

실제 사용 예시

계좌 잔액 조회 — 읽기 중 변경 방지

-- 트랜잭션 A: 잔액 확인 (읽는 동안 변경되면 안 됨)
BEGIN;
SELECT balance FROM accounts WHERE id = 1 FOR SHARE;
-- balance = 1000
-- 이 시점에 다른 트랜잭션이 UPDATE하면 대기

-- 업무 처리 (예: 리포트 생성)
-- ...

COMMIT;  -- 공유 락 해제
-- 트랜잭션 B: 잔액 수정 시도
BEGIN;
UPDATE accounts SET balance = 500 WHERE id = 1;
-- ↑ 트랜잭션 A가 공유 락을 보유 중이면 대기
-- 트랜잭션 A가 COMMIT/ROLLBACK하면 실행됨
COMMIT;

재고 확인 — 여러 사용자가 동시에 조회

-- 사용자 A, B, C 모두 동시에 재고 조회 가능
SELECT stock FROM products WHERE id = 100 FOR SHARE;
-- 공유 락은 서로 호환되므로 동시 실행 O

-- 재고 수정은 모든 공유 락이 해제될 때까지 대기
UPDATE products SET stock = stock - 1 WHERE id = 100;

락의 범위 (Lock Granularity)

┌─── 데이터베이스 락 ──────────────────────────────┐
│ 전체 DB에 락 → 동시성 최저                        │
├─── 테이블 락 ───────────────────────────────────┤
│ 특정 테이블 전체에 락                             │
├─── 페이지 락 ───────────────────────────────────┤
│ 디스크 페이지 단위 락                             │
├─── 행 락 (Row Lock) ────────────────────────────┤
│ 특정 행에만 락 → 동시성 최고 (가장 많이 사용)      │
└─────────────────────────────────────────────────┘
-- 행 수준 공유 락 (특정 행만)
SELECT * FROM accounts WHERE id = 1 FOR SHARE;
-- id = 1인 행만 락 → 다른 행은 자유롭게 접근 가능

-- 테이블 수준 락 (MySQL)
LOCK TABLES accounts READ;   -- 테이블 전체 공유 락
LOCK TABLES accounts WRITE;  -- 테이블 전체 배타 락
UNLOCK TABLES;

데드락 (Deadlock) 주의

트랜잭션 A                         트랜잭션 B
───────────                       ───────────
행 1에 S락 획득                    행 2에 S락 획득
    │                                  │
    ▼                                  ▼
행 2에 X락 요청 → 대기              행 1에 X락 요청 → 대기
    │         ↖                  ↗         │
    └───────── 서로 대기 ─────────┘
              (DEADLOCK!)
-- 데드락 발생 예시

-- 트랜잭션 A
BEGIN;
SELECT * FROM accounts WHERE id = 1 FOR SHARE;
-- ... 다른 작업 ...
UPDATE accounts SET balance = 500 WHERE id = 2;  -- 대기

-- 트랜잭션 B
BEGIN;
SELECT * FROM accounts WHERE id = 2 FOR SHARE;
-- ... 다른 작업 ...
UPDATE accounts SET balance = 300 WHERE id = 1;  -- 대기 → DEADLOCK
해결:
DBMS가 데드락을 감지하면 한쪽 트랜잭션을 강제 롤백
→ "Deadlock found when trying to get lock" 에러 발생
→ 애플리케이션에서 재시도 로직 필요

락과 트랜잭션 격리 수준

격리 수준공유 락 동작
READ UNCOMMITTED락 없이 읽기 (Dirty Read 허용)
READ COMMITTED읽기 완료 즉시 공유 락 해제
REPEATABLE READ트랜잭션 끝까지 공유 락 유지
SERIALIZABLE범위까지 공유 락 (가장 엄격)
-- REPEATABLE READ에서
BEGIN;
SELECT * FROM accounts WHERE id = 1 FOR SHARE;
-- 트랜잭션이 끝날 때까지 S락 유지
-- 다른 트랜잭션이 이 행을 수정 불가
COMMIT;  -- 여기서 S락 해제

낙관적 락 vs 비관적 락

[ 비관적 락 (Pessimistic Lock) ]
"충돌이 날 것 같아, 미리 락을 걸자"
→ SELECT ... FOR SHARE / FOR UPDATE
→ 공유 락, 배타 락이 비관적 락의 구현

[ 낙관적 락 (Optimistic Lock) ]
"충돌이 별로 없을 거야, 일단 작업하고 나중에 확인하자"
→ 버전 번호나 타임스탬프로 충돌 감지
→ 락을 실제로 걸지 않음
-- 낙관적 락 예시 (버전 기반)
SELECT id, balance, version FROM accounts WHERE id = 1;
-- balance = 1000, version = 5

UPDATE accounts
SET balance = 500, version = version + 1
WHERE id = 1 AND version = 5;
-- version이 5가 아니면 0 rows affected → 충돌 감지
항목비관적 락 (공유/배타)낙관적 락
충돌 예상높음낮음
락 오버헤드있음없음
충돌 시대기재시도
적합한 상황쓰기 많음, 충돌 빈번읽기 많음, 충돌 드묾

핵심 정리

공유 락은 여러 트랜잭션이 동시에 읽기를 허용하되, 쓰기는 차단하는 락입니다. 공유 락끼리는 호환되어 동시 보유가 가능하지만, 배타 락과는 충돌하여 대기합니다. SQL에서는 SELECT ... FOR SHARE로 명시적으로 공유 락을 걸 수 있으며, 읽기 작업 중 데이터 변경을 방지하여 데이터 일관성을 보장합니다. 다만 여러 트랜잭션이 공유 락을 보유한 상태에서 배타 락을 요청하면 데드락이 발생할 수 있으므로, 락 순서를 일관되게 유지하거나 타임아웃을 설정하는 등의 주의가 필요합니다.

CSLOG에서 보기