낙관적 vs 비관적

잠금 전략은 충돌 빈도와 실패 비용을 같이 보고 고른다

읽기/쓰기 비율만으로 결정하면 놓치는 것이 많습니다. 충돌이 얼마나 자주 나는지, 실패 후 재시도가 쉬운지, 한 번 실패했을 때 업무 손실이 큰지부터 놓고 낙관적 검사, 비관적 잠금, 원자적 갱신, 큐 직렬화를 조합합니다.

의사결정 맵

X축은 충돌 빈도, Y축은 실패 비용이다. 경계가 애매하면 더 짧은 트랜잭션과 더 강한 제약을 먼저 추가한다.

잠금 전략 선택 좌표 충돌 빈도와 실패 비용을 두 축으로 두고 낙관적 잠금, 원자적 조건부 갱신, 비관적 잠금, 큐 직렬화를 배치한다. 충돌 빈도 높아짐 실패 비용 커짐 낙관적 잠금 낮은 충돌 / 재시도 쉬움 조건부 갱신 손실 방지 제약 짧은 비관적 잠금 높은 충돌 / 짧은 대기 큐 직렬화 높은 손실 / 불변식 보호 낮음 높음 낮음 높음
낮은 충돌 · 낮은 손실 낙관적 잠금 버전 컬럼으로 커밋 시점에 충돌을 감지한다.
낮은 충돌 · 높은 손실 조건부 갱신 제약과 WHERE 조건으로 잘못된 성공을 막는다.
높은 충돌 · 낮은 손실 짧은 비관적 잠금 잠깐 기다리는 편이 재시도보다 싸면 대기를 통제한다.
높은 충돌 · 높은 손실 큐 직렬화 동시성을 줄여 재고·정산 같은 불변식을 지킨다.
실패 비용
충돌 낮음대부분 한 번에 성공
충돌 높음같은 대상에 동시 접근
낮음재시도와 안내가 쉬움
낙관적 잠금 버전 컬럼으로 커밋 시점에 감지

JPA @Version이나 조건부 갱신으로 충돌을 확인하고, 제한된 횟수만 다시 읽어 재시도한다.

짧은 비관적 잠금 잠깐 기다리는 편이 더 싸다

SELECT FOR UPDATE, NOWAIT, SKIP LOCKED, lock wait timeout으로 대기 시간을 통제한다.

높음틀리면 업무 손실이 큼
원자적 조건부 갱신 제약과 WHERE 조건으로 실패를 막는다

충돌은 드물어도 손실이 크면 stock >= qty 같은 조건과 유니크 제약으로 잘못된 성공을 차단한다.

직렬화 또는 큐 동시성을 줄여 불변식을 지킨다

재고 차감, 정산, 좌석 배정처럼 실패 비용이 큰 흐름은 짧은 비관적 잠금이나 큐 직렬화를 검토한다.

낙관적 후보 긴 사용자 작업, 낮은 충돌

수정 화면을 오래 열어두는 업무는 잠금을 오래 잡지 않는다. 저장 시점에 버전 불일치를 감지하고 새 값을 보여준 뒤 재시도한다.

비관적 후보 짧은 트랜잭션, 높은 충돌

같은 행을 자주 갱신하면 먼저 잠그고 빨리 끝낸다. 여러 테이블을 잠글 때는 접근 순서를 통일해 데드락을 줄인다.

주의 인덱스와 DBMS 차이

인덱스가 부실하면 잠금 범위가 커진다. InnoDB의 record/gap/next-key lock, PostgreSQL과 Oracle의 대기 옵션 차이를 확인한다.

재고 차감 실패 비용이 큰 대표 사례
1 조건부 update로 음수 재고를 먼저 차단한다.
2 영향받은 행이 0이면 품절 또는 충돌로 보고 재시도하지 않는다.
3 핫스팟 상품은 큐 또는 짧은 비관적 잠금으로 직렬화한다.
작게 시작하는 안전한 형태
UPDATE stock SET qty = qty - :qty WHERE item_id = :id AND qty >= :qty;

이 방식은 낙관/비관 이름보다 “잘못된 성공을 만들지 않는다”는 불변식을 먼저 코드로 표현한다.