이지은님의 블로그
250318 - 동시성 제어와 락의 개념과 비교, 데드락과 해결방법(공유락, 독점락, 비관적 및 낙관적 동시성 제어) 본문
250318 - 동시성 제어와 락의 개념과 비교, 데드락과 해결방법(공유락, 독점락, 비관적 및 낙관적 동시성 제어)
queenriwon3 2025. 3. 18. 23:16▷ 오늘 배운 것
스탠다드반 세션에서 배운 동시성 제어에 대해서 정리해보려고 한다. 추후 레디스를 사용할 때 락에 대한 내용도 함께 사용할 것 같다.
<<목차>>
1. 동시성 제어
1) 락(lock)
2) 동시성 제어의 종류
3) 데드락 (Deadlock)
1. 동시성 제어
1) 락(lock)
락이란, 특정 자원에 대한 접근을 제어하여 동시성을 관리하고 데이터의 일관성 및 무결성을 유지하는 메커니즘이다.
락의 장점
1️⃣ 자원 공유: 여러 사용자나 프로세스가 동일한 데이터에 동시에 접근하려고 할 때, 락은 이 중 하나만 데이터를 수정할 수 있도록 허용하여 데이터의 일관성을 보장한다.
2️⃣ 데이터 무결성: 데이터베이스에서 트랜잭션이 실행되는 동안 데이터 무결성을 유지하기 위해 락 사용
락의 종류
1️⃣ 공유 락(Shared Lock)
- 데이터를 읽을 때 사용한다.
- 공유 락이 걸린 데이터는 다른 사용자는 읽을 수 있으나, 수정은 불가하다.(안전한 읽기)
2️⃣ 독점 락(Exclusive Lock)
- 데이터를 수정할 때 사용한다.
- 독점 락이 걸린 데이터는 해당 락을 소유한 사용자만이 데이터를 읽거나 수정할 수 있고 다른 사용자는 접근이 불가하다.
2) 동시성 제어의 종류
1️⃣ 비관적 동시성 제어 (Pessimistic Concurrency Control)
비관적 동시성 제어란, 충돌이 발생할 것이라고 비관적으로 가정하고 데이터에 접근하기 전에 락을 사용하여 해당 데이터를 보호한다.
데이터를 읽거나 수정하는 동안 해당 데이터를 잠그고(락), 다른 트랜잭션이 해당 데이터에 접근하는 것을 방지한다.
- 데이터베이스 트랜잭션이 길거나 충돌 가능성이 높을 때 사용
- 장점: 데이터 무결성 보장
- 단점: 락으로 성능 저하, 데드락 가능성
public class AccountService {
@PersistenceContext
private EntityManager entityManager;
@Transactional
public void withdraw(Long accountId, double amount) {
// Account 엔티티를 찾고 비관적 락을 건다. 조회를 하는 동안 다른 수정 트랜잭션이 접근할 수 없음
Account account = entityManager.find(Account.class, accountId, LockModeType.PESSIMISTIC_WRITE);
if (account.getBalance() >= amount) { // 잔액 확인
account.setBalance(account.getBalance() - amount); // 금액 차감
} else {
throw new RuntimeException("Insufficient funds");
}
// 변경사항을 데이터베이스에 반영
entityManager.merge(account);
}
}
EntityManager를 사용하여 특정 accountId에 해당하는 Account 엔티티를 검색하고, PESSIMISTIC_WRITE 락을 걸어 다른 트랜잭션이 동시에 같은 데이터를 수정하지 못하게 한다.
2️⃣ 낙관적 동시성 제어 (Optimistic Concurrency Control)
낙관적 동시성 제어란, 충돌이 자주 발생하지 않는다고 낙관적으로 가정하고, 트랜잭션이 데이터를 커밋할 때만 충돌을 검사한다.
데이터에 대한 락 없이 트랜잭션을 수행하고, 커밋 시점에 변경 사항이 있는지 확인하여 충돌을 검사
- 주로 읽기 작업이 많고, 쓰기 작업이 상대적으로 적을 때 적합
- 장점: 동시성 수준이 높고 시스템의 처리 성능에 미치는 영향이 비교적 적음
- 단점: 충돌 발견 시 이미 수행한 모든 작업을 취소(롤백)해야 할 수 있음
@Entity
@Table(name = "accounts")
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column
private double balance;
@Version
private int version; //낙관적 락
}
@Transactional
public void withdraw(Long accountId, double amount) {
EntityManager em = entityManagerFactory.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
try {
Account account = em.find(Account.class, accountId);
if (account.getBalance() >= amount) {
account.setBalance(account.getBalance() - amount);
em.merge(account);
transaction.commit(); // 변경사항 커밋, 여기서 버전 충돌 검사 발생
} else {
throw new RuntimeException("Insufficient funds");
}
} catch (OptimisticLockException ole) {
transaction.rollback();
System.out.println("Transaction conflict detected: " + ole.getMessage());
} finally {
em.close();
}
}
데이터베이스에서 계좌의 잔액을 갱신하고 커밋할때 버전 충돌을 검사한다.
만약 다른 트랜잭션이 이미 해당 엔티티를 수정하여 버전 번호가 변경된 경우, OptimisticLockException이 발생하고 롤백된다.
비관적과 낙관적 동시성 제어의 비교
비관적 동시성 제어 (Pessimistic Concurrency Control) | 낙관적 동시성 제어 (Optimistic Concurrency Control) | |
가정 | 충돌이 발생할 것이라고 비관적으로 가정 | 충돌이 자주 발생하지 않는다고 낙관적으로 가정 |
락 | 데이터를 읽거나 수정하는 동안 해당 데이터를 잠금 | 데이터에 대한 락 없이 트랜잭션을 수행 |
사용하는 경우 | 데이터베이스 트랜잭션이 길거나 충돌 가능성이 높을 때 사용 | 주로 읽기 작업이 많고, 쓰기 작업이 상대적으로 적을 때 적합 |
장점 | 데이터 무결성 보장 | 동시성. 시스템의 처리 성능에 미치는 영향이 비교적 적음 |
단점 | 락으로 성능 저하, 데드락 가능성 | 충돌 발견 시 이미 수행한 모든 작업을 취소(롤백)해야 할 수 있음 |
3) 데드락 (Deadlock)
데드락이란? 두 개 이상의 트랜잭션이 서로의 락을 기다리면서 영원히 대기 상태에 빠지는 현상을 말함(교착상태, 무한 대기 상태)

예시)
1️⃣ 행 레벨 Lock
: 트랜잭션에서 UPDATE를 할 경우 읽기 일관성을 유지하기 위해 행 레벨로 락을 설정.
2️⃣ UNDO SEGMENT Buffer 확보 및 정보 기록(롤백 정보 확보)
3️⃣ 데이터 UPDATE
: 다른 트랜잭션에서 SELECT할 경우 정보를 UNDO BLOCK에서 조회할 수 있도록하고 데이터 UPDATE 작업
4️⃣ COMMIT & 행 Unlock
이때, 어떠한 기능의 API의 트랜잭션에서 DB Lock이 걸려 무한 대기하여 실행되지 않는다면(DeadLock)
(update를 할때 타임아웃 에러 발생 및 트랜잭션에서 데드락 발생 등..)
둘 이상의 프로세스가 다른 프로세스가 점유하고 있는 자원을 서로 기다릴 때 무한 대기에 빠지는 상황을 말한다.(트랜잭션의 교착상태) 두개의 트랜잭션간에 각각의 트랜잭션이 가지고 있는 리소스의 Lock을 획득하려고 할 때 발생
하나의 트랜잭션이 테이블 A 업데이트 후 B를 업데이트 하려고 할 때, 다른 트랜잭션이 B를 작업할 경우 완료할 때까지 대기한다. 길어질시 교착상태 발생
해결방법?
- 데드락 발생 가능성을 인정하면서도 적절하게 회피하기
- 데드락 발생을 허용하지만 데드락을 탐지하여, 데드락에서 회복하기
- 트랜잭션 진행방향을 같은 방향으로 처리 (테이블A 업데이트 후 테이블B 업데이트, 블로킹)
- 트랜잭션 처리속도를 최소화
- SET LOCK_TIMEOUT: 잠금해제 시간 설정