Transaction
데이터는 정확하고, 안전하게 다뤄져야 서비스가 안정적으로 운영된다.
ex. 두 사용자가 재고가 3개가 남은 물품을 거의 동시에 2개씩 구매하려고 한다면...?
// 현재 재고를 확인한다.
select stock from item where id = 2;
// 재고가 구매하려는 수량보다 많음을 확인한다.
// todo
// 주문 정보를 추가한다.
insert into customer_order(customer_id)
values (1);
select customer_id from customer_order where id = 1;
// 주문 정보에 아이템을 추가한다.
insert into order_item(order_id, item_id, count)
values (61, 2, 2);
// 구매한 아이템의 수량을 줄인다.
update item set stock = stock - 2
where id = 2;
> 이때 두번째로 처리되는 사용자는 실제로 존재하지 않는 물품을 구매하게 될 것이고,,,, 이럴 때 서비스가 안정적이지 않게 된다.
=> 이를 해결하기 위해 Transaction의 개념 등장.
1. Transaction
1) 트랜잭션
: 어떤 작업을 하기 위한 일련의 SQL문의 모음을 지칭.
- START TRANSACTION (SQLite는 begin transaction) : 이 이후의 SQL문들을 트랜잭션으로 취급한다.
- ROLLBACK : 트랜잭션으로 실행되었던 INSERT, UPDATE가 취소되며 트랜잭션 실행 이전 상태로 돌아간다.
- COMMIT : 트랜잭션에서 있었던 일이 전부 데이터베이스에 정상적으로 반영된다. (트랜잭션이 성공적으로 수행되었다는 의미.)
2) 트랜잭션 특징 ACID
- Atomicity 원자성 : 트랜잭션이 하나의 작업 단위로 취급되어야 한다.
- Consistency 일관성 : 트랜잭션 시작 전과 후의 데이터베이스 상태가 일관성을 유지해야 한다. (트랜잭션의 실행 결과로 데이터베이스 제약사항을 만족하지 못한다면 트랜잭션은 롤백되어야 한다)
- Isolation 격리성 : 각각의 트랜잭션은 서로 독립되어야 한다. (다른 트랜잭션의 연산을 참조 할 수 없다.)
- Durability 지속성 : 트랜잭션 마무리 후 그 결과가 영속성을 가져야 한다. 이후 작업들의 실패나 시스템 오류로부터 데이터가 안전하게 지켜져야 한다.
3) @Transactional
: 특정 클래스나 메서드에 붙여서 해당 메서드에서의 데이터베이스 작업을 트랜잭션으로 만들어주는 어노테이션.
위의 경우처럼 서비스 메서드를 하나 만들어보았다.
@Service
@Slf4j
@RequiredArgsConstructor
public class ShopService {
private final CustomerRepository customerRepository;
private final OrderRepository orderRepository;
private final ItemRepository itemRepository;
private final OrderItemRepository orderItemRepository;
@Transactional
public void createOrder() {
// 고객 1이
Customer customer = customerRepository.findById(1L).orElseThrow();
// 주문을 한다.
Order newOrder = orderRepository.save(Order.builder().customer(customer).build());
// 상품 2를
Item item = itemRepository.findById(2L).orElseThrow();
// 주문과 상품을 엮고 10개의 수량을 산다는 주문상품 내역을 저장한다.
orderItemRepository.save(OrderItem.builder().order(newOrder).item(item).count(10).build());
// 만약 재고가 10개보다 적지않다면
if (!(item.getStock() < 10)) {
// 재고 갯수를 줄이고, 저장
item.setStock(item.getStock() - 10);
itemRepository.save(item);
}
// 10개보다 적다면 예외 발생.
else throw new IllegalStateException();
}
}
만약 @Transactional이 붙지 않는다면 10개 이하로 남는다고 한들 그 전의 상황들은 이미 저장된 상황이기 때문에 다시 돌아갈 수 없다. 그래서 요청을 계속 한다면 주문상품 내역이 계속 늘어난다.
>> @Transactional을 붙이면 Exception이 발생하는 순간 Transaction 시작 이전으로 돌아가기 때문에 아무리 요청을 보내도 새로운 주문상품 내역이 늘어나지 않는다.
4) 트랜잭션 전파 (Transaction Propagation)
: @Transactional이 붙은 메서드 내부에서 다른 @Transactional 메서드를 호출하는 경우가 있다.
이 때 호출한 메서드를 현재 트랜잭션의 일부로 취급할 것인지? 아니면 별개의 트랜잭션으로 취급할 것인지를 결정하는 것이 트랜잭션 전파이다.
- 이때 propagation을 정의한다.
- REQUIRED(기본값) : 현재 트랜잭션이 실행된 상태라면 해당 트랜잭션의 일부로 실행되며, 아니라면 새로운 트랜잭션 생성.
- SUPPORTS : 트랜잭션이 실행되었다면 그 일부로 실행되지만, 아니라면 트랜잭션 없이 실행.
- MANDATORY : 트랜잭션이 실행되었다면 그 일부로 실행되지만, 트랜잭션이 없는 경우 예외 발생
- NEVER : 트랜잭션이 실행되었다면 예외 발생. 없을 경우 트랜잭션 없이 실행.
- NOT_SUPPORTED : 트랜잭션이 실행된 상태라면, 해당 트랜잭션을 대기시킨 뒤 트랜잭션 없이 실행.
- REQUIRES_NEW : 트랜잭션이 실행된 상태라면, 해당 트랜잭션을 대기시킨 뒤 트랜잭션 새로 실행.
현재 트랜잭션이 실행되었다면 (TXN 1) | 트랜잭션이 없다면 | |
REQUIRED | TXN1 KEEP | TXN2 NEW |
SUPPORTS | TXN1 KEEP | NO (없이 실행) |
MANDATORY | TXN1 KEEP | EXCEPTION |
NEVER | EXCEPTION | NO (없이 실행) |
NOT_SUPPORTED | TXN1 WAIT | NO (없이 실행) |
REQUIRES_NEW | TXN1 WAIT | TXN2 NEW |
5) 격리 수준
: ACID 특징의 Isolation 격리성은 트랜잭션이 서로의 상태를 확인할 수 없게 격리되어 트랜잭션이 완료되어야 커밋한 내용을 확인할 수 있는 특징이다.
실제에서는 격리 수준을 조절하여 > 다른 트랜잭션에서 있었던 일들을 어느정도까지 확인할 수 있는지 조절가능.
- 발생할 수 있는 문제
- Dirty Read : 수정되었지만, 아직 커밋되지 않은 수정 사항들을 읽게 되는 문제. (한쪽 트랜잭션에서 일어났던 일이 커밋 이전에 읽어진 다음, 롤백이 일어나게 된다면 다른 트랜잭션에서는 잘못된 정보를 바탕으로 작업이 수행될 수 있다.)
- Non-Repeatable Read : 한번 읽어들였던 데이터 열의 내용이 다른 트랜잭션의 커밋으로 인해 변경되는 문제.
- Phantom Read : 한번의 트랜잭션에서 두 번의 조회를 했는데, 다른 트랜잭션의 커밋으로 인해 다른 데이터를 반환받게 되는 문제.
- 격리 수준 : @Transactional의 isolation으로 설정. 격리 수준이 높아질수록 문제는 덜 발생하지만, 성능이 떨어진다.
- DEFAULT(기본값) : DBMS의 설정을 따른다.
- READ_UNCOMMITTED : 서로 다른 트랜잭션의 커밋되지 않은 데이터를 조회 가능 (Dirty Read, Non-Repeatable Read, Phantom Read) (가장 낮은 수준의 격리)
- READ_COMMITED : 커밋된 데이터를 조회 가능 (Non-Repeatable Read, Phantom Read)
- REPEATABLE_READ : 커밋되지 않은 정보를 가진 열의 읽기 방지 (Phantom Read)
- SERIALIZABLE : 트랜잭션을 순차적으로 실행.
'Programming > Database' 카테고리의 다른 글
눈물의 DB 연결 (2) | 2024.05.03 |
---|---|
Redis (1) | 2024.02.14 |
JDBC (1) | 2023.12.20 |
정규화 Normalization와 조인 Join (1) | 2023.12.19 |
[연습문제 모음] (0) | 2023.12.18 |