Database Lock
- 데이터베이스가 제공하는 Lock을 활용하여 데이터 정합성을 맞출 수 있다.
💡Pessimistic Lock (비관적 락)

- 실제 데이터에 직접적으로 Lock을 거는 방식
- 다른 트랜잭션은 Lock이 해제되기 전까지 데이터를 읽을 수 없다.
1) 특징 (참고 자료)
- 데이터 충돌을 피하기 위해 먼저 Lock을 걸고 작업을 진행
- 일반적으로 Exclusive Lock(X Lock, 배타적 락)을 사용해 다른 트랜잭션의 Read/Write을 방지한다.
- 은행 계좌, 항공권 좌석, 콘서트 티켓 등 충돌이 빈번하고 데이터 정합성이 중요한 환경에서 유용하다.
2) 장점 (참고 자료)
- 충돌이 빈번하게 일어날 경우 Optimistic Lock보다 성능이 좋을 수 있다.
- Lock을 통해 update를 제어하기 때문에 데이터의 정합성이 보장된다.
(데이터를 수정할 때 미리 잠가(lock) 버리기 때문에, 여러 사람이 동시에 건드려도 데이터가 꼬이지 않는다는 의미)
3) 단점 (참고 자료)
- 서로 다른 자원을 쥐고 놓지 않는 데드락(Deadlock) 발생 가능
4) 사용 방법
- Spring Data JPA에서는 @Lock 어노테이션을 사용하여 구현할 수 있다.
public interface StockRepository extends JpaRepository<Stock, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select s from Stock s where s.id = :id")
Stock findByIdWithPessimistic(Long id);
}
➡️ @Lock(LockModeType.PESSIMISTIC_WRITE)
- 해당 데이터에 대해 쓰기 잠금을 설정하여, 다른 트랜잭션이 해당 데이터를 읽거나 수정하지 못하게 막는다.
💡Optimistic Lock (낙관적 락)

- 데이터를 읽고 수정할 때까지 락을 걸지 않고, 수정 시점에서 충돌 여부를 확인
- 실제로 락을 이용하지 않고 version 을 이용해 데이터 정합성을 맞춤
1) 특징 (참고 자료)
- 먼저 데이터를 읽은 후에 update를 할 때 내가 읽은 버전이 맞는 확인하며 업데이트한다.
- 일반적으로 버전번호 또는 타임스탬프를 활용해 변경 여부를 확인
- 동시성이 높은 환경에서 유용하며, 충돌 가능성이 낮은 경우 적합
2) 장점 (참고 자료)
- 별도의 Lock을 잡지 않아 Pessimistic Lock보다 성능상으로 이점이 있다.
- 충돌이 발생하면 다시 데이터를 읽고 재시도해야 한다.
- update가 실패했을 때 재시도 로직을 개발자가 직접 작성해야 하는 번거로움이 있다.
3-1) 예외처리를 하는 이유 (참고 자료)
1. 사용자 A가 Stock 데이터를 조회 (SELECT)
- id=1, quantity=100, version=1
2. 사용자 B가 동일한 Stock 데이터를 조회 (SELECT)
- id=1, quantity=100, version=1
3. 사용자 A가 Stock을 업데이트 (UPDATE)
UPDATE stock SET quantity=90, version=2 WHERE id=1 AND version=1;
- 성공적으로 업데이트됨 (version=2로 증가)
4. 사용자 B가 Stock을 업데이트 시도 (UPDATE)
UPDATE stock SET quantity=80, version=2 WHERE id=1 AND version=1;
- version=1을 가진 데이터가 없으므로 예외 발생 (OptimisticLockException)
4) 사용 방법
- Stock Entity Class 에 version 컬럼 추가
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Stock {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long productId;
private Long quantity;
@Version
private Long version;
//생략...
}
➡️ 주의 : javax.persistence 패키지에 있는 @Version 어노테이션을 사용
- Repository
public interface StockRepository extends JpaRepository<Stock, Long> {
@Lock(LockModeType.OPTIMISTIC)
@Query(" select s from Stock s where s.id = :id ")
Stock findByIdWithOptimisticLock(Long id);
}
➡️ Spring Data JPA에서는 @Lock 어노테이션을 사용하여 구현할 수 있습니다.
- facade 생성
@Component
public class OptimisticLockStockFacade {
private final OptimisticLockStockService optimisticLockStockService;
public OptimisticLockStockFacade(OptimisticLockStockService optimisticLockStockService){
this.optimisticLockStockService = optimisticLockStockService;
}
public void decrease(Long id, Long quantity) throws InterruptedException{
while (true) { //업데이트를 실패했을 때 재시도를 해야함
try {
optimisticLockStockService.decrease(id, quantity);
//정상적으로 업데이트 되면 빠져나감
break;
} catch (Exception e) {
//수량 감소 실패 시, 재시도
Thread.sleep(50);
}
}
}
}
➡️ Optimistic Lock은 실패했을 경우 재시도를 해야하기 때문에 facade(퍼사드)와 같은 재시도 로직을 생성해야한다.
💡Named Lock
1-1) 특징
- 이름을 가진 메타데이터를 사용하는 metadata locking 방법이다. (참고 자료)
- 이름을 가진 lock을 획득한 후 해제할 때까지 다른 세션이 이 lock을 획득할 수 없도록 한다. (참고 자료)
- 트랜잭션이 종료 될 때 Lock이 자동으로 해제되지 않으므로 별도의 명령어로 해제가 필요하다. (참고 자료)
- MySQL에서는 GET_LOCK() 함수를 사용해서 사용자가 정의한 이름(name)을 기준으로 lock을 획득할 수 있다. (공식 문서)
1-2) Named Lock 데이터 적용 과정 (참고 자료)

- Named Lock은 DB에 락을 걸지 않고, 별도의 공간에 락을 건다.
- session-1이 1이라는 이름으로 락을 건다면 session-1이 1을 해지한 후에 락을 얻을 수 있다.
2) 장점
3) 단점 (참고 자료)
- 락을 획득하지 못하면 대기하거나 재시도 로직 필요
- 트랜잭션 종료 시 자동으로 Lock 해제 되지 않아 Lock을 걸고 해제하는 별도 로직이 필요
4) 사용 방법 (참고 자료)
- Named Lock 을 수행하는 repository 를 생성 한다.
public interface LockRepository extends JpaRepository<Stock, Long> {
@Query(value = "select get_lock(:key, 3000)", nativeQuery = true)
void getLock(String key);
@Query(value = "select release_lock(:key)", nativeQuery = true)
void releaseLock(String key);
}
- Lock 획득 해제하는 별도의 명령을 위해 Facade 패턴을 생성한다.
@Component
public class NamedLockStockFacade {
private final LockRepository lockRepository;
private final StockService stockService;
public NamedLockStockFacade(LockRepository lockRepository, StockService stockService){
this.lockRepository = lockRepository;
this.stockService = stockService;
}
//decrease 메서드
@Transactional
public void decrease(Long id, Long quantity){
try {
//lock 획득
lockRepository.getLock(id.toString());
//재고 감소
stockService.decrease(id, quantity);
}finally {
//모든 로직이 종료되었을 때, lock 해제
lockRepository.releaseLock(id.toString());
}
}
}
- StockService propagation 수정
@Transactional(propagation = Propagation.REQUIRES_NEW)
public synchronized void decrease(Long id, Long quantity){
// Stock 조회
Stock stock = stockRepository.findById(id).orElseThrow();
// 재고를 감소
stock.decrease(quantity);
// 갱신된 값을 저장
stockRepository.saveAndFlush(stock);
}
➡️ 부모의 트랜잭션과 별도로 실행되어야 하기 때문에 propagation을 변경해주어야 한다.
❗️Propagation(전파) 란?
현재 메서드가 트랜잭션을 어떻게 사용할지 결정하는 규칙
ex.
@Transactional(propagation = Propagation.REQUIRED) -> 기존 트랜잭션 사용, 없으면 새로 생성
@Transactional(propagation = Propagation.REQUIRES_NEW) -> 항상 새로운 트랜잭션 생성, 기존 트랜잭션은 일시 정지
- connection pool size 수정
- 같은 데이터 소스를 사용하기 위해 connection pool size를 변경합니다.
maximum-pool-size: 40
참고 자료
1) 블로그 : SpringBoot 동시성 이슈 해결방법[3] - Lock
https://velog.io/@kiteof_park/SpringBoot-pessimistic-lock
SpringBoot 동시성 이슈 해결방법[3] - Lock
synchronized의 한계와 Lock의 종류 - Pessimistic Lock, Optimistic Lock, Named Lock
velog.io
2) 블로그 : [Java/Spring] 재고 시스템으로 알아보는 동시성 이슈와 해결 방법
https://velog.io/@ha02e/Java-Spring-ConcurrencyIssue#2-database-lock
[Java/Spring] 재고 시스템으로 알아보는 동시성 이슈와 해결 방법
인프런 재고시스템으로 알아보는 동시성이슈 해결방법 강의를 듣고 작성한 글입니다.
velog.io
3) 블로그 : [Java/Spring] 재고시스템으로 알아보는 동시성 이슈 해결 방법
https://girokeulhaja.tistory.com/107
[Java/Spring] 재고시스템으로 알아보는 동시성 이슈 해결 방법🔧
❗해당 포스팅은 인프런에서 제공해 주는 강의 내용을 개인적으로 정리하였음을 알려드립니다. 재고시스템으로 알아보는 동시성이슈 해결방법 강의 | 최상용 - 인프런최상용 | , 동시성 이슈
girokeulhaja.tistory.com
4) 유튜브 : 락의 두 종류: 비관적 락 vs 낙관적 락
https://www.youtube.com/watch?v=oJrVl6QKzHw
5) 블로그 : 인프런) 재고시스템으로 알아보는 동시성 이슈 해결 (1)
https://dodop-blog.tistory.com/463
인프런) 재고시스템으로 알아보는 동시성 이슈 해결 (1)
동시성 이슈 해결을 위한 인프런 강의를 듣고 실습해보았다. https://www.inflearn.com/course/%EB%8F%99%EC%8B%9C%EC%84%B1%EC%9D%B4%EC%8A%88-%EC%9E%AC%EA%B3%A0%EC%8B%9C%EC%8A%A4%ED%85%9C/dashboard 재고시스템으로 알아보는 동
dodop-blog.tistory.com
6) MySQL Docs : Locking Functions
https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html
MySQL :: MySQL 8.0 Reference Manual :: 14.14 Locking Functions
This section describes functions used to manipulate user-level locks. Table 14.19 Locking Functions GET_LOCK(str,timeout) Tries to obtain a lock with a name given by the string str, using a timeout of timeout seconds. A negative timeout value means infin
dev.mysql.com
'Projects > HubEleven' 카테고리의 다른 글
| [동시성 처리] 동시성 문제 (Race Condition) 해결 방법 3가지 관점 - (3) Redis Distributed Lock (0) | 2026.03.11 |
|---|---|
| [동시성 처리] 동시성 문제 (Race Condition) 해결 방법 3가지 관점 - (1) Synchronized (0) | 2026.03.06 |
| [동시성 처리] 재고 감소 통합 테스트 코드 6 - Optimistic Lock(낙관적 락) 멀티 스레드 테스트 코드 (1) | 2026.02.24 |
| [동시성 처리] Optimistic Lock (낙관적 락) 재시도 로직 (0) | 2026.02.23 |
| [동시성 처리] DB Lock : Optimistic Lock (낙관적 락) - 실습 (0) | 2026.02.20 |