낙관적 락(Optimistic Lock)
- 데이터를 읽을 때는 락을 걸지 않고, 데이터를 변경할 때 충돌을 검사하는 방식 
 
- 데이터를 읽은 후 변경 시에는 다시 락을 확인하여 충돌이 없는지 확인한다. 충돌이 발생한 경우, 다른 트랜잭션이 해당 데이터를 변경했기 때문에 롤백을 수행하거나 충돌을 해결하는 메커니즘을 사용 
 
- 데이터 갱신 충돌이 드물거나 트랜잭션 충돌이 일어날 확률이 낮을 때 사용한다.
 
- MySQL에서는 낙관적 락을 위해 버전 관리 컬럼(Versioning Column)을 사용함
 
JPA에서 낙관적 락 사용
@Entity
public class Account {
    @Id
    private Long id;
    private int balance;
    @Version
    private int version;
    public Account(Long id, int balance, int version) {
        this.id = id;
        this.balance = balance;
        this.version = version;
    }
    public Long getId() {
        return id;
    }
    public int getBalance() {
        return balance;
    }
    public int getVersion() {
        return version;
    }
}
- @Version 어노테이션을 이용해서 간단하게 적용할 수 있고 아래에 나올 LockModeType을 명시적으로 선언해서 적용할 수도 있다.
 
- 위와 같은 엔티티가 존재하고 id가 1인 사람에게 500원을 송금한 로직이 실행되었다고 가정해보자
 
UPDATE accounts 
SET balance = balance + 500, version = version + 1 
WHERE id = 1 AND version = 1
- 쿼리는 위와 같이 실행이 된다.
 
- 만약 다른 트랜잭션에 의해 이미 버전정보가 바뀌었으면, 업데이트 로우(Row)수가 0이 반환되면서 충돌감지가 되어 예외(OptimisticLockException)가 발생하게 된다.
 
비관적 락(Pessimistic Lock)
- 데이터에 대한 동시 액세스를 허용하지 않고, 데이터를 읽거나 변경할 때 즉시 락을 거는 방식. 
 
- 락을 획득한 트랜잭션이 해당 데이터를 사용 중이기 때문에 다른 트랜잭션은 해당 데이터에 대한 작업을 대기해야 한다.
 
- MySQL에서는 SELECT 문장에 FOR UPDATE 절을 추가하여 특정 데이터에 대한 비관적 락을 설정할 수 있다.
 
- 이 방식은 데이터 갱신 충돌이 자주 발생하거나 동시 액세스가 많은 환경에서 유용합니다.
 
JPA에서 비관적 락 사용
public void updateAccountBalanceWithPessimisticLock(EntityManager entityManager, Long accountId, int newBalance) {
    try {
        entityManager.getTransaction().begin();
        Account account = entityManager.find(Account.class, accountId, LockModeType.PESSIMISTIC_FORCE_INCREMENT);
        account.setBalance(account.getBalance + newBalance);
        entityManager.getTransaction().commit();
    } catch (Exception e) {
        entityManager.getTransaction().rollback();
        throw e;
    }
}
SELECT * FROM accounts WHERE id = 1 FOR UPDATE
UPDATE accounts SET balance = balance + 500 WHERE id = 1
UPDATE accounts 
SET version = version + 1 
WHERE id = 1 AND version = 1
데이터에 적용할 수 있는 다양한 잠금 모드를 나타내는 열거형 LockModeType
- NONE: 잠금 모드를 지정하지 않음. 다른 트랜잭션이 데이터에 접근할 수 있다
 
- OPTIMISTIC: 낙관적 잠금을 수행. 엔티티를 조회할 때 잠금을 설정하지 않으며, 트랜잭션 커밋 시에 충돌을 감지함.
 
- OPTIMISTIC_FORCE_INCREMENT: 낙관적 잠금을 수행하면서, 버전 관리 컬럼의 값을 증가시킨다.
 
- PESSIMISTIC_READ: 비관적 읽기 잠금을 수행. 다른 트랜잭션에서 데이터를 읽는 것을 차단한다
 
- PESSIMISTIC_WRITE: 비관적 쓰기 잠금을 수행 다른 트랜잭션에서 데이터를 읽거나 수정하는 것을 차단한다
 
- PESSIMISTIC_FORCE_INCREMENT: 비관적 쓰기 잠금을 수행하면서, 버전 관리 컬럼의 값을 증가시킨다. 트랜잭션 커밋 시에 버전 충돌을 감지할 수 있음
 
장단점
낙관적 락(Optimistic Locking)과 비관적 락(Pessimistic Locking)은 데이터베이스에서 동시에 접근하는 트랜잭션 간의 충돌을 제어하는 방법입니다. 각각의 방식은 다음과 같은 장단점을 가지고 있습니다.
1) 낙관적 락(Optimistic Locking):
- 장점
- 동시성이 높아진다. 동시에 여러 트랜잭션이 데이터에 접근하고 변경할 수 있기 때문에 시스템의 처리량이 향상됨. (반드시 되는 건 아님)
 
- 락을 사용하지 않기 때문에 다른 트랜잭션에서 데이터를 읽을 수 있다.
 
- 충돌이 발생했을 때 롤백을 피하고 충돌을 해결할 수 있는 기회를 제공한다.
 
 
- 단점:
- 충돌이 발생할 경우 롤백이 발생할 수 있다. 다른 트랜잭션에서 변경한 데이터와 충돌이 발생하면 예외가 발생하고 롤백 발생
 
 
비관적 락(Pessimistic Locking):
- 장점:
- 데이터를 접근하는 동안 다른 트랜잭션이 접근하지 못하도록 제어할 수 있다. 데이터의 일관성과 동시성을 보장할 수 있다.
 
- 데이터에 대한 잠금을 설정하여 다른 트랜잭션의 변경을 차단함으로써 충돌을 방지할 수 있습다.
 
 
- 단점:
- 동시성이 낮아진다. 데이터를 잠그기 때문에 다른 트랜잭션에서 해당 데이터에 접근하거나 변경하는 것이 제한된다.
 
- 락을 사용하므로 다른 트랜잭션이 해당 데이터를 읽을 수 없다.
 
- 잠금을 설정한 상태에서 해당 트랜잭션의 작업이 오래 걸리면 다른 트랜잭션들이 대기하게 되어 시스템 성능이 저하될 수 있다.
 
 
즉, 낙관적 락은 동시성이 중요하고 충돌이 잘 발생하지 않을 것으로 예상되는 경우에 적합하며, 비관적 락은 데이터의 일관성과 동시성이 중요하고 충돌이 발생할 가능성이 높은 경우에 적합함.