Transaction

2024. 2. 2. 15:43
728x90

데이터는 정확하고, 안전하게 다뤄져야 서비스가 안정적으로 운영된다.

 

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 : 트랜잭션을 순차적으로 실행. 
728x90

'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

BELATED ARTICLES

more