DB

낙관적 락(Optimistic Lock)과 비관적 락(Pessimistic Lock)

KJihun 2025. 10. 17. 16:34
728x90

 

동시에 여러 트랜잭션이 같은 데이터를 수정하려고 할 때, 데이터의 정합성(Consistency)이 깨질 수 있다.
이를 방지하기 위해, 트랜잭션 간의 충돌을 제어하는 동시성 제어(Concurrency Control) 기법이 필요하다.

세마포어(Semaphore)와 뮤텍스(Mutex)는 운영체제(OS) 수준에서 스레드 간 자원 접근을 제어하기 위해

시스템 락을 거는 방법이다.
반면, 낙관적 락비관적 락서버 애플리케이션 혹은 데이터베이스(DB) 레벨에서 적용되는 동시성 제어방식이다.

즉, 단일 프로세스 내의 스레드 동기화가 아니라,
여러 트랜잭션이 같은 데이터를 수정하려 할 때 데이터의 정합성(Consistency)을 유지하기 위한 방법이라 할 수 있다.

 

 


 

 

비관적 락 (Pessimistic Lock)

비관적 락은 “충돌이 자주 일어날 것이다”라고 비관적으로 가정하고,
데이터를 읽거나 수정하기 전에 먼저 락을 걸어 다른 트랜잭션의 접근을 차단하는 방식이다.

즉, 한 트랜잭션이 데이터를 조회하거나 수정하는 동안,
다른 트랜잭션은 그 데이터에 접근할 수 없다.

 

 

 

동작 방식

  1. 트랜잭션이 데이터를 읽기 or 수정 전에 lock을 획득한다.
  2. 다른 트랜잭션이 해당 데이터에 접근하려 하면, 락이 해제될 때까지 대기(blocking) 된다.
  3. 작업이 완료되면 트랜잭션이 커밋 또는 롤백되며 락이 해제된다.

 

 

 

장점

  • 데이터 정합성을 강력하게 보장한다.
  • 구현이 비교적 간단하고 직관적이다.

 

단점

  • 락으로 인한 대기 시간이 길어져 시스템 전체의 성능 저하를 유발할 수 있다.
  • 락을 너무 오래 유지하면 데드락(Deadlock)이 발생할 위험이 크다.

 

 

 

 

 

DB 락 적용 예시

BEGIN;

SELECT * 
FROM account 
WHERE id = 1 
FOR UPDATE;  -- 베타적 락

UPDATE account 
SET balance = balance - 100 
WHERE id = 1;

COMMIT;

FOR UPDATE를 사용하면 Exclusive Lock(배타적 락)이 걸려 다른 트랜잭션은 이 데이터가 커밋될 때까지 접근할 수 없다.

 

 

서버 락 적용 예시

private final Object lock = new Object();

public void withdraw(Long accountId, int amount) {
    synchronized (lock) {
        Account account = accountRepository.findById(accountId);
        account.decreaseBalance(amount);
        accountRepository.save(account);
    }
}
 

한 스레드가 synchronized 블록에 들어가면 다른 스레드는 해당 블록이 끝날 때까지 대기한다.
즉, 서버 프로세스 내에서만 뮤텍스(Mutex) 수준의 락 제어가 이루어진다.

 

 

 


 

 

 

낙관적 락 (Optimistic Lock)

낙관적 락은 "충돌이 잘 일어나지 않을 것이다"라고 낙관적으로 가정하고,
트랜잭션이 데이터를 수정할 때 버전(Version)을 비교하여 충돌 여부를 확인하는 방식이다.

즉, 락을 걸지 않고 자유롭게 접근하되, 커밋 시점에 충돌을 검증한다.

 

 

 

 

동작 방식

  1. 데이터를 읽을 때, 현재 버전(version) 정보를 함께 가져온다.
  2. 수정 시, WHERE 조건에 버전을 포함하며, 업데이트 성공시 version 값증가시킨다.
  3. 버전이 일치하지 않을 경우 업데이트하지 않으며, UPDATE 결과가 0건이 되어 충돌이 감지된다.
  4. 충돌 시에는 OptimisticLockException 또는 개발자가 정의한 예외를 발생시켜 재시도 로직을 처리한다.

 

 

 

장점

  • 락을 걸지 않으므로 대기 시간이 없어 높은 처리량(Throughput)을 기대할 수 있다.
  • 데드락의 위험이 없다.

 

단점

  • 커밋 시점에 충돌을 확인하므로, 롤백 처리 비용이 발생할 수 있다.
  • 충돌이 자주 발생한다면, 계속되는 재시도로 인해 오히려 성능이 저하될 수 있다.
  • 재시도 로직을 개발자가 직접 구현해야 하는 복잡성이 있다.

 

 

 

 

DB 락 적용 예시

-- 1. 데이터 조회 시 현재 version을 함께 가져온다.
SELECT id, balance, version 
FROM account 
WHERE id = 1;

-- 2. 업데이트 시 version을 비교하며, 일치하면 balance와 version을 동시에 갱신한다.
UPDATE account
SET balance = balance - 100,
    version = version + 1
WHERE id = 1
  AND version = 5;
// 동시성 발생 시 처리 예시(Mybatis)
int updated = jdbcTemplate.update(
    "UPDATE account SET balance = ?, version = version + 1 WHERE id = ? AND version = ?",
    newBalance, accountId, currentVersion
);

if (updated == 0) {
    throw new OptimisticLockConflictException("다른 트랜잭션에서 데이터가 변경되었습니다.");
}

 

 

 

 

 

서버 락 적용 예시

@Entity
public class Account {
    @Id
    private Long id;

    @Version  // JPA가 자동으로 version 필드 기반 낙관적 락을 관리
    private int version;

    // ....
}
@Service
public class AccountService {

    @Transactional
    public void withdraw(Long accountId, int amount) {
        Account account = accountRepository.findById(accountId)
                 .orElseThrow(() -> new RuntimeException("계좌를 찾을 수 없습니다."));

        account.decreaseBalance(amount);
        // JPA가 flush 시점에 version 비교 → 불일치 시 OptimisticLockException 발생
    }
}
// 예외 처리 및 재시도 로직
try {
    accountService.withdraw(1L, 1000);
} catch (OptimisticLockException e) {
    // 재시도 로직 또는 사용자 알림 처리
    throw new CustomRetryException("다른 사용자가 데이터를 수정했습니다. 다시 시도해주세요.");
}

 

 

  • @Version 필드가 있으면 JPA는 내부적으로 WHERE id=? AND version=? 조건으로 UPDATE를 수행한다.
  • version이 일치하지 않으면 OptimisticLockException이 발생한다.
  • 프레임워크가 던지는 예외를 잡아 재시도 또는 사용자에게 알림하는 로직을 추가할 수 있다.

 

 


 

 

 

비교요약 : 비관적 락 vs. 낙관적 락

항목 (Category) 비관적 락 (Pessimistic Lock) 낙관적 락 (Optimistic Lock)
기본 가정 충돌이 자주 발생할 것이라고 가정한다. 충돌이 거의 발생하지 않을 것이라고 가정한다.
락 시점 데이터에 접근하는 시점에 락을 건다. 데이터를 수정하고 커밋하는 시점에 충돌을 확인한다.
성능 락으로 인한 대기 때문에 성능 저하가 발생할 수 있다. 락 대기가 없어 높은 처리량을 제공한다.
데이터 정합성 락을 통해 강력하게 보장한다. 커밋 시점에 버전을 비교하여 보장한다.
주요 단점 데드락 발생 가능성, 성능 저하. 잦은 충돌 시 롤백 및 재시도 비용 증가.
구현 복잡도 비교적 간단하다. 재시도 로직 구현이 필요하여 복잡할 수 있다.

 

 

 


 

 

 

어떤 락을 선택해야 할까? 🤔

비관적 락이 적합한 경우

  • 쓰기 충돌이 빈번하게 발생할 것으로 예상되는 환경
  • 금액, 재고 등 데이터 정합성이 매우 중요하여 롤백 비용이 대기 비용보다 큰 경우
  • 트랜잭션 처리 시간이 짧아 락 유지 시간이 길지 않은 경우

 

낙관적 락이 적합한 경우

  • 읽기 작업이 대부분이고 쓰기 작업은 드물게 발생하는 환경
  • 충돌이 발생해도 재시도 처리가 간단하거나 사용자에게 알림으로 충분한 경우
  • 높은 동시성과 처리량이 중요한 서비스

 

비관적 락 낙관적 락 중 어느 것이 절대적으로 우수하다고 말할 수는 없다.

애플리케이션의 특성과 데이터의 성격에 따라 적절한 방식을 선택하는 것이 중요하다.

'DB' 카테고리의 다른 글

분산 락(distributed lock) 이란?  (0) 2025.10.16
[Redis] 레디스 자료형  (0) 2025.06.24
[DB] Connection Pool, HikariCP  (0) 2025.03.27
FETCH JOIN  (0) 2025.03.18
[CS] DBMS  (0) 2023.07.08