DB 락은 MSA, JPA, 트랜잭션 동시성 문제에서 자주 터지는 주제라 중요하다.
여러 트랜잭션이 동일한 데이터에 동시에 접근할 때,
데이터의 정합성(Consistency)을 지키기 위해 잠금(Lock)을 거는 메커니즘을 말한다.
즉, 충돌 나는 동시 접근을 막기 위한 ‘문 잠그기’라고 생각하면 된다.
DB마다 종류가 다양하지만, 일단은 **실무에서 가장 중요한 세 가지만 중심으로 알아보았다.
SELECT ... FOR SHARE 또는 MySQL의 LOCK IN SHARE MODE 형태특징:
데이터를 수정(UPDATE/DELETE) 하려는 트랜잭션이 거는 락
걸리는 순간:
특징:
SELECT ... FOR UPDATE트랜잭션이 다음을 실행할 때 lock이 걸린다:
→ Row Lock
→ “해당 로우를 쓸 예정이니 잠깐 아무도 건드리지 마”
→ 자동으로 Exclusive Lock 걸림
→ 삽입되는 PK row에 lock 걸림
→ 단, 중복 체크 위해 “gap lock”이 생길 수 있음(MySQL InnoDB)
서로 락을 잡은 상태에서 상대가 가진 락을 기다리며 영원히 끝나지 않는 상태.
예시:
| 트랜잭션 A | 트랜잭션 B |
|---|---|
| row1 lock | row2 lock |
| row2 lock 대기 | row1 lock 대기 |
→ 서로 상대방이 풀기를 기다림 → Deadlock
Deadlock found when trying to get lock; try restarting transaction 오류DB는 트랜잭션을 Lock wait timeout으로 실패시킨다.
MySQL 기본 값:
innodb_lock_wait_timeout = 50초
장시간 락 때문에 장애가 발생하는 것을 막기 위함.
→ 따라서 동시성 이슈 발생 가능 (Lost Update)
@Version
private Long version;
→ 충돌 시 예외 발생 → 재시도 로직 사용
@Lock(PESSIMISTIC_WRITE)
@Query("select u from User u where u.id = :id")
User findByIdForUpdate(Long id);
→ 실제 DB의 SELECT FOR UPDATE 실행
→ 동시 수정 방지 확실
| 상황 | 추천 락 |
|---|---|
| 재고 감소, 은행 계좌 이체 | 비관적 락(PESSIMISTIC_WRITE) |
| 동시에 수정될 가능성이 낮음 | 낙관적 락(Optimistic Lock) |
| 목록 조회, 읽기 위주 | Shared Lock(없음도 가능) |
| 주문번호 고유값 생성 등 | Row Lock + Unique Index |
서비스가 여러 개로 나뉘면, DB 로우 기반 락만으로는 해결 안 된다. 그래서:
RLock lock = redissonClient.getLock("stock:" + productId);
try {
if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {
// 재고 감소
}
} finally {
lock.unlock();
}
UPDATE/DELETE는 자동으로 X Lock 발생