핵심 개념
두 락 방식의 차이는 "충돌이 일어날 것이라고 가정하냐, 아니냐" 입니다.
비관적 락낙관적 락
| 가정 | 충돌이 자주 일어난다 | 충돌이 거의 없다 |
| 방식 | DB 레벨에서 미리 잠금 | 버전으로 충돌 감지 후 재시도 |
| 단점 | 성능 저하 (대기) | 충돌 시 재시도 비용 |
방식 1 — UPDATE로 처리하는 비관적 락
PessimisticLock1
가장 단순한 방식입니다. UPDATE like_count = like_count + 1 쿼리 자체가 원자적(atomic)으로 실행되기 때문에 DB가 행 레벨 잠금을 자동으로 처리합니다.
@Transactional
public void likePessimisticLock1(Long articleId, Long userId) {
articleLikeRepository.save(
ArticleLike.create(snowflake.nextId(), articleId, userId)
);
int result = articleLikeCountRepository.increase(articleId);
if (result == 0) {
// UPDATE된 행 없음 → 최초 요청이므로 INSERT
articleLikeCountRepository.save(
ArticleLikeCount.init(articleId, 1L)
);
}
}
1) increase() 내부에서 UPDATE ... SET like_count = like_count + 1 실행
2) DB가 해당 행에 Row Lock을 자동으로 설정 → 동시 요청이 순차 처리됨
3) 반환값이 0이면 행이 없었다는 뜻 → INSERT로 초기화
2) DB가 해당 행에 Row Lock을 자동으로 설정 → 동시 요청이 순차 처리됨
3) 반환값이 0이면 행이 없었다는 뜻 → INSERT로 초기화
방식 2 — SELECT FOR UPDATE 비관적 락
PessimisticLock2
조회 시점에 명시적으로 잠금을 걸어 읽기부터 쓰기까지 전 과정을 보호합니다.
@Transactional
public void likePessimisticLock2(Long articleId, Long userId) {
articleLikeRepository.save(...);
ArticleLikeCount count =
articleLikeCountRepository
.findLockedByArticleId(articleId) // SELECT ... FOR UPDATE
.orElseGet(() -> ArticleLikeCount.init(articleId, 0L));
count.increase();
articleLikeCountRepository.save(count);
}
SELECT FOR UPDATE 실행
해당 행에 잠금을 획득 — 다른 트랜잭션은 이 지점에서 대기
엔티티 수정
잠금을 유지한 채로 increase() 호출
save() → 트랜잭션 커밋
커밋 후 잠금 해제 → 대기 중이던 다음 트랜잭션 진행

방식 3 — 낙관적 락
OptimisticLock
잠금 없이 조회 후, 저장 시점에 @Version 필드로 충돌을 감지합니다.
@Transactional
public void likeOptimisticLock(Long articleId, Long userId) {
articleLikeRepository.save(...);
ArticleLikeCount count =
articleLikeCountRepository
.findById(articleId) // 일반 SELECT (잠금 없음)
.orElseGet(() -> ArticleLikeCount.init(articleId, 0L));
count.increase();
articleLikeCountRepository.save(count);
// 내부: UPDATE ... WHERE version = 기존버전
// 버전 불일치 → OptimisticLockException!
}
트랜잭션 A — 조회 (version = 1)
잠금 없이 읽고 수정 진행
트랜잭션 A — 저장 성공 (version 1 → 2)
UPDATE ... WHERE version = 1 → 성공
트랜잭션 B — 저장 실패
version = 1을 기대했지만 이미 2 → OptimisticLockException → 재시도 필요
세 방식 한눈에 비교
방식잠금 시점쿼리주의사항
| PessimisticLock1 | UPDATE 실행 시 | UPDATE (원자적) | 최초 요청 동시성 이슈 |
| PessimisticLock2 | SELECT 시점 | SELECT FOR UPDATE | 대기 시간 증가 가능 |
| OptimisticLock | 커밋 시점 | UPDATE WHERE version | 재시도 로직 필요 |
'인프런' 카테고리의 다른 글
| 대용량 데이터에서 인덱스에 대한 고민. (0) | 2026.04.11 |
|---|