"충돌이 일어날 것"이라고 가정하고 미리 잠그는 전략(pessimistic lock).
방법
BEGIN;
SELECT * FROM stock WHERE id = 1 FOR UPDATE; -- 배타 락 획득
-- 이 순간부터 다른 트랜잭션은 이 행을 수정할 수 없다(대기)
UPDATE stock SET qty = qty - 1 WHERE id = 1;
COMMIT; -- 락 해제
| 구문 | 락 종류 | 다른 트랜잭션이 |
|---|---|---|
FOR UPDATE | 배타 락(X) | 읽기도 쓰기도 대기 |
FOR SHARE | 공유 락(S) | 읽기는 가능, 쓰기는 대기 |
JPA에서는 @Lock(LockModeType.PESSIMISTIC_WRITE) 로 건다.
언제 유리한가 — 경쟁이 심할 때
낙관적 락은 충돌하면 되돌리고 재시도한다. 경쟁이 심하면 재시도가 폭증한다.
인기 상품 재고 차감, 동시 요청 100건 낙관적 락 → 1건 성공, 99건 실패 → 재시도 → 1건 성공, 98건 실패 → …
- 총 시도 횟수가 5000회에 육박 비관적 락 → 100건이 순서대로 대기하며 처리. 시도는 100회
대가와 위험
① 대기가 처리량을 떨어뜨린다 락을 쥔 시간만큼 다른 요청이 멈춘다. → 트랜잭션을 최대한 짧게. 특히 락을 쥔 채 외부 API를 호출하면 최악이다.
② 데드락 여러 행을 잠글 때 순서가 엇갈리면 서로를 기다린다.
T1: A 잠금 → B 요청
T2: B 잠금 → A 요청 ← 교착
→ 항상 같은 순서로 잠그면 크게 줄어든다(예: id 오름차순).
③ 락 타임아웃
무한 대기를 막기 위해 innodb_lock_wait_timeout(기본 50초)이 있다.
운영에서는 더 짧게 잡아 빨리 실패시키는 편이 낫다.
인덱스가 없으면 테이블 전체가 잠긴다 — 중요한 함정
InnoDB의 행 락은 인덱스 레코드에 걸린다.
SELECT * FROM orders WHERE memo = 'x' FOR UPDATE; -- memo에 인덱스가 없다면?
→ 풀스캔하며 훑은 모든 행에 락 → 사실상 테이블 락
락을 걸 컬럼에 인덱스가 있는지 반드시 확인해야 한다.
면접 답변 골격
"경쟁 정도로 고릅니다. 충돌이 드물면 낙관적 락으로 대기를 없애고, 한 행에 몰리는 구간은 비관적 락으로 재시도 폭증을 막습니다. 어느 쪽이든 트랜잭션을 짧게 하고 락 컬럼에 인덱스가 있는지 확인하는 것이 전제입니다."