-
데이터베이스 데드락, 공유락, 베타락: 꼭 알아야 할 실무 필수 개념 (JPA)성장과정(dev)/Spring + Java + JPA 2024. 12. 13. 16:10
Lock 의 종류
- 비관적 락(Pessimistic Lock): 항상 락을 사용. 데드락이 발생할 가능성이 높아질 수 있습니다.
- 낙관적 락(Optimistic Lock): 데이터 충돌이 발생했을 때만 처리. 이 방식이 데드락을 방지한다고 해도 한계가 있음. 잦은 충돌 발생 시 트랜잭션 반복 재시도로 성능 저하, 트랜젝션이 길 경우 충돌 가능성 상승. 따라서 낮은 경쟁환경에서 효과적
특성을 반영하면 낙관적 락, 비관적 락은 실무에서 보통 어떤 상황에 사용하나요?
낙관적 락
- 전자상거래 시스템에서 사용자 장바구니의 데이터 관리.
- 게시판, 블로그 등 대부분 읽기 위주의 애플리케이션.
비관적 락
- 은행 거래 시스템, 재고 관리 시스템.
비관적 락의 주요 유형 2가지 공유 락, 베타 락
- 공유 락 (Shared Lock) = 읽기 락(Read Lock)
- 읽기 작업을 수행하는 동안 락을 설정
- 조회한 데이터가 트랜잭션 내내 변경되지 않음을 보장
- 여러 트랜잭션이 동시에 데이터를 읽을 수 있지만, 쓰기 작업은 제한
- MySQL 사용 예: SELECT ... LOCK IN SHARE MODE
- 배타 락 (Exclusive Lock) = 쓰기 락(Write Lock)
- 쓰기 작업을 수행하는 동안 락을 설정
- 배타 락을 획득한 트랜잭션은 해당 데이터에 대한 독점권을 가짐
- 하나의 트랜잭션만 데이터에 접근 가능.
- 예: SELECT ... FOR UPDATE (MySQL)
베타 락 사용 시 주로 발생하는 데드락
데드 락(Dead Lock)이란 교착 상태로, 두개 이상의 트랜잭션이 서로 필요로 하는 데이터의 락을 점유하고 있어서 무한히 대기하는 상황.
> 순환 대기 (Circular Wait)
- 두 개 이상의 트랜잭션이 서로가 점유한 자원을 기다리며 원형 대기를 형성.
- 예:
- 트랜잭션 A: 자원 1 → 자원 2 요청 (대기)
- 트랜잭션 B: 자원 2 → 자원 1 요청 (대기)
데드락 어떻게 해결하나요?
- 트랜잭션에서 락 획득 순서를 일관되게 하기
> 모든 트랜잭션에서 1번 데이터, 2번 데이터 순으로 락을 획득할 시 데드 락이 발생하지 않음 - 락 타임 아웃을 설정합니다.
//락 타임아웃 걸기 Map<String, Object> properties = new HashMap<>(); //이건 yaml 에 정의하고 AppProperties 정의하는 걸로 대체하자 properties.put("javax.persistence.lock.timeout", 5000); // 5초 타임아웃 User user = entityManager.find(User.class, userId, LockModeType.PESSIMISTIC_WRITE, properties);
Lock 의 실행주체는?
- 일반적으로 애플리케이션에서 데이터베이스 관리 시스템(DBMS)은 데이터의 일관성과 무결성을 유지하기 위해 Lock을 설정
- JPA에서 제공하는 Lock 기능 역시 결국 DBMS에 Lock 설정을 요청하여 동작
JPA 에서 비관적 락 사용하기
JPA 에서는 @Lock 어노테이션을 통해 비관적 락과 추가 세부옵션(공유 락, 배타 락 등 구현방식 설정) 지원.
비관적 락은 주로 PESSIMISTIC_WRITE (배타 락)을 사용
//JPA 에서의 pessimistic lock // PESSIMISTIC_READ, PESSIMISTIC_WRITE Account account = entityManager.find(Account.class, accountId, LockModeType.PESSIMISTIC_WRITE);
트랜잭션에서 락 획득 순서를 일관되게 하기(JPA 에서의 예제)
@Service public class UserService { @PersistenceContext private EntityManager entityManager; @Transactional public void updateUserAccount(Long userId, Long accountId, double amount, String newUserName) { // Account 엔티티에 배타 락 설정 Account account = entityManager.find(Account.class, accountId, LockModeType.PESSIMISTIC_WRITE); // User 엔티티에 배타 락 설정 User user = entityManager.find(User.class, userId, LockModeType.PESSIMISTIC_WRITE); // 비즈니스 로직: 계좌 잔액 업데이트 double newBalance = account.getBalance() + amount; if (newBalance < 0) { throw new IllegalArgumentException("잔액이 부족합니다."); } account.setBalance(newBalance); // 비즈니스 로직: 사용자 이름 업데이트 user.setName(newUserName); // EntityManager가 트랜잭션 종료 시점에 변경 사항을 자동으로 반영(Flush)합니다. } }
JPA 에서 낙관적 락 사용하기
// 엔티티에 @Version 필드 추가 (테이블에 version 컬럼 추가) @Entity public class User { @Id @GeneratedValue private Long id; private String name; @Version private Integer version; // Getter, Setter }
'성장과정(dev) > Spring + Java + JPA' 카테고리의 다른 글
nodeJS 개발자에서 다시 JAVA 개발자로, Java 9 ~ Java21 주요 변경사항들 (1) 2024.10.24 [intellij] jpa + gradle + junit4 테스트 중 에러발생 (0) 2020.11.26 [jpa] mariaDB 연동 (0) 2020.11.26 log4j sql로그 가독성 높이기 (0) 2020.09.18 log4j 설정 (0) 2020.09.17