데이터베이스 용어 사전
트랜잭션갱신 손실 · 낙관적 락 · 비관적 락

Lost Update

읽고-고쳐-쓰기가 겹쳐 한쪽 변경이 덮여 사라지는 현상. 격리수준만으로는 부족해 락이나 버전으로 따로 막는다.

두 트랜잭션이 읽고 → 고치고 → 쓰기를 겹쳐서, 한쪽의 변경이 다른 쪽에 덮여 사라지는 현상.

어떻게 사라지나

재고 10개

T1: SELECT stock → 10
T2: SELECT stock → 10
T1: UPDATE stock = 10 - 1 = 9   커밋
T2: UPDATE stock = 10 - 1 = 9   커밋      ← T1 의 차감이 사라졌다

2개를 팔았는데 재고는 9다

격리수준을 올려도 잘 안 막힌다. 두 트랜잭션 모두 정상적으로 커밋된 값을 읽고 정상적으로 썼기 때문이다. 그래서 따로 막아야 한다.

두 가지 전략

-- 비관적 락(pessimistic): 읽는 순간 잠근다
BEGIN;
SELECT stock FROM product WHERE id = 42 FOR UPDATE;   -- 행 잠금
UPDATE product SET stock = stock - 1 WHERE id = 42;
COMMIT;
-- 낙관적 락(optimistic): 잠그지 않고, 쓸 때 버전이 그대로인지 확인
UPDATE product SET stock = stock - 1, version = version + 1
 WHERE id = 42 AND version = 7;
-- 영향 행이 0이면 누가 먼저 바꾼 것 → 재시도
비관적 락낙관적 락
언제충돌이 잦을충돌이 드물
방식먼저 잠그고 작업쓸 때 버전 확인
비용락 경합·데드락재시도 로직 필수

가장 단순한 해법 — DB가 계산하게 한다

-- 읽지 않고 바로 계산시키면 읽기-쓰기 경합 자체가 없다
UPDATE product SET stock = stock - 1 WHERE id = 42 AND stock > 0;

현재값 기반 누적(재고 차감·조회수 증가)은 이 방식이 가장 안전하고 빠르다. AND stock > 0으로 음수 방지까지 한 번에 된다.

면접 함정

  • "격리수준을 SERIALIZABLE로 올리면 해결" → 되긴 하지만 전체 성능을 크게 희생한다. 문제 지점만 락·버전으로 막는 게 정석이다.
  • "낙관적 락이 더 좋다" → 충돌이 잦으면 재시도가 폭증해 오히려 느려진다. 충돌 빈도로 고른다.

낙관적 락의 재시도까지 포함한 형태

-- 애플리케이션 의사코드
for (int i = 0; i < 3; i++) {
    int v = SELECT version FROM product WHERE id = 42;
    int n = UPDATE product SET stock = stock - 1, version = version + 1
              WHERE id = 42 AND version = :v;
    if (n == 1) break;          // 성공
    // n == 0 → 그사이 남이 바꿨다 → 다시 읽고 재시도
}

n == 0을 성공으로 착각하는 것이 가장 흔한 버그다. JPA 같은 ORM은 이 경우 예외를 던져 주지만, 직접 SQL을 쓸 때는 영향 행 수를 반드시 확인해야 한다.

FOR UPDATE의 변형들

-- 기다리지 않고 즉시 실패 — 사용자에게 "다른 사람이 수정 중" 을 바로 알릴 때
SELECT * FROM product WHERE id = 42 FOR UPDATE NOWAIT;

-- 잠긴 행은 건너뛴다 — 작업 큐를 여러 워커가 나눠 가질 때
SELECT * FROM job_queue WHERE status='READY'
 ORDER BY id LIMIT 10 FOR UPDATE SKIP LOCKED;

SKIP LOCKED는 DB를 작업 큐로 쓸 때의 핵심이다. 워커 여러 대가 같은 테이블에서 서로 다른 행을 집어 가므로 경합 없이 병렬 처리된다. PostgreSQL 9.5+, MySQL 8.0+에서 쓸 수 있다.

왜 원자적 UPDATE가 가장 나은가

  • 읽고-고쳐-쓰기 — 왕복 2번 + 그 사이에 경합 창이 열린다
  • 원자적 UPDATE — 왕복 1번 + 경합 창이 없다 (DB 가 행 락 안에서 계산)

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 트랜잭션 — ACID·격리수준·이상현상(PostgreSQL·MySQL)