초이로그

트랜잭션 본문

우아한테크코스/테코톡 정리

트랜잭션

수연초이 2022. 10. 8. 17:14
[10분 테코톡]🌼 예지니어스의 트랜잭션
[10분 테코톡] 🙊 에이든의 트랜잭션 메커니즘
[10분 테코톡] 🐤 샐리의 트랜잭션
을 정리한 글

트랜잭션은 왜 필요할까???

가장 대표 예시인 계좌 이체 결제 과정을 생각해보자. 해당 과정에서 오류가 발생한다면, 

  • 구매자의 계좌에서 돈이 출금된 뒤, DB가 다운된다.
  • 구매자의 계좌에서 돈이 출금되지 않았는데, 판매자에게 돈이 입금된다.
  • 출금도 입금도 되지 않는다.

등의 오류가 발생할 것이다. 이를 해결하기 위해서는 어중간한 상태 없이 전부 없었던 일로 해주자. (= 트랜잭션)

 

트랜잭션이란

두 작업이 모두 실행되거나 실행되지 않아야한다.

  • 여러 쿼리를 논리적으로 하나의 작업으로 묶어주는 것
  • 쿼리들이 한꺼번에 모두 실행되거나 아예 아무 쿼리도 실행되지 않게 하는 것
  • 트랜잭션은 사용자 혹은 시스템 상의 실수가 있더라도 데이터베이스가 데이터를 안정적으로 보장할 수 있도록 한다.

 

  • commit: 하나의 트랜잭션이 모두 실행. 트랜잭션으로 묶인 모든 쿼리가 성공하여 트랜잭션 쿼리 결과를 실제 DB에 반영
  • rollback: 아무 쿼리도 실행되지 않는 것. 쿼리 실행 결과를 취소하고 디비를 트랜잭션 이전 상태로 되돌림

 

트랜잭션의 성질 - ACID

트랜잭션이 안전하게 수행된다는 것을 보장하기 위한 성질. 

그러나 ACID 성질은  트랜잭션이 이론적으로 보장해야하는 성질이며, 실제로는 성능을 위해 손실 보장이 완화되기도 한다. 예를 들어, 독립성을 완벽하게 보장하려고 한다면, 동일 데이터에 100개의 연결이 접근하는 경우, 100개의 연결을 순차적으로 해결해야한다. 동시성이 매우 떨어질 것이다. 

 

Atomicity (원자성)

  • 트랜잭션은 DB에 모두 반영되거나, 전혀 반영되지 않아야 한다.
  • 완료되지 않은 트랜잭션의 중간 상태를 DB에 반영해서는 안된다.
  • 중간 단계까지 실행되고 실패하는 일이 없도록 보장한다.
  • ex) 원자성이 보장된다면, 구매자의 계좌에서는 돈이 빠져나갔는데 판매자 계좌에 돈이 들어오지 않는 일은 없을 것이다.

 

Consistency(일관성)

  • 트랜잭션 작업 처리 결과는 항상 일관성 있어야 한다.
  • 데이터베이스는 항상 일관된 상태로 유지되어야한다.
  • DB에 여러 제약 조건에 맞는 상태를 보장한다.
  • ex) DB에 마이너스 통장을 허락하지 않는다는 조건이 있다면 조건이 위배되는 경우 트랜잭션이 바로 종료된다.
  • ex2) 어떤 컬럼의 속성이 수정된 경우, trigger를 통해 일괄적으로 모든 DB에 적용해야한다.

 

Isolation(독립성)

  • 둘 이상의 트랜잭션이 동시 실행되고 있을 때, 어떤 트랜잭션도 다른 트랜잭션 연산에 끼어들 수 없다.
  • 각각의 트랜잭션은 서로 간섭 없이 독립적으로 이루어져야 한다.
  • ex) 구매자의 계좌에서 돈이 빠져나가고 판매자의 계좌에 돈이 아직 들어가지 않은 DB 상황을 다른 트랜잭션이 조회하면 안된다.

 

Durability(지속성)

  • 트랜잭션이 성공적으로 완료되었으면 결과는 영구히 반영되어야 한다.
  • ex) 한번 송금이 성공하면, 은행 시스템에 문제가 생기더라도 송금이 성공한 상태로 복구할 수 있어야한다. (로그 활용)

 

트랜잭션 동작 방식

앞서, 데이터베이스는 대략적으로 쿼리 처리기, 데이터 캐시, 로그 캐시, 데이터파일, 로그 파일 등이 있다는 개념을 알아두자.

실행될 쿼리

BEGIN TRAN
UPDATE accounts
SET balance = balance - 1000
WHERE user = '구매자';
UPDATE accounts
SET balance = balance + 1000
WHERE user = '판매자';
COMMIT TRAN
  1. 쿼리에 트랜잭션을 걸게 되면, BEGIN TRAN과 COMMIT TRAN 명령어가 선언된다.
  2. 구매자의 계좌에서 돈을 인출하는 첫번째 쿼리가 쿼리 처리기에 의해 통과된다. 
  3. 해당 쿼리에서는 구매자에 대한 데이터가 필요한데 캐시에 해당 데이터가 존재하지 않는다.
  4. 데이터 파일에서 필요한 데이터를 가져온다.
  5. 데이터 캐시에 필요한 데이터가 로드된다. (아래 표는 데이터가 로드된 데이터 캐쉬 상태이다)
User Balance
구매자 10,000

6. update 쿼리 실행 전, 로그에 기록이 된다.

  6-1. UnDo 로그: 변경 전의 값을 기록한다. 로그 번호와 account 테이블에 어떤 데이터의 값의 이전 상태를 기록

  로그_1 accounts.balance 구매자 10000

  6-2. ReDo 로그: 변경 후의 값을 기록한다. 트랜잭션 시작 명시와 데이터가 어떤 값으로 변경될 것인지 기록'

  트랜잭션_1 START
  트랜잭션_1 UPDATE accounts.balance 구매자 0

7. 이후 데이터 캐시의 값을 변경

User Balance
구매자 0 

8. 다음 쿼리도 마찬가지로 실행된다. 쿼리 처리기에 의해 두번째 쿼리가 통과한다.

9. 데이터 캐시에 필요한 데이터가 없으므로 데이터 파일로부터 데이터를 로드된다.

User Balance
구매자 0 
판매자 0

10. update 전 로그로 기록한다.

  10-1. UnDo 로그

  로그_1 accounts.balance 구매자 10000
  로그_1 accounts.balance 판매자 0

  10-2. ReDo 로그

  트랜잭션_1 START
  트랜잭션_1 UPDATE accounts.balance 구매자 0
  트랜잭션_1 UPDATE accounts.balance 판매자 10000

11. 데이터 캐싱

User Balance
구매자 0 
판매자 10,000

이렇게 트랜잭션의 한 단위가 완료된다.

 

만약 트랜잭션에서 오류가 발생한다면? 

롤백을 발생시켜 Undo 로그를 통해 역순으로 기록이 되었으므로 이를 통해 해결한다. 데이터 복구가 가능하다. 아래의 표는 복구된 데이터이다.

User Balance
구매자 10,000
판매자 0

 

만약 롤백이 아닌, OS 문제 또는 정전 문제 등 예상치 못한 오류가 발생하는 경우?

Redo 로그를 사용하여 데이터를 모두 일관성 있게 만든다. 그 이후, Undo로그를 통해 필요 없는 값들은 복구시킨다. 

예를 들어, 트랜잭션 START는 있지만 COMMIT은 존재하지 않는다면, undo 로그를 통해 되돌릴 수 있다.

 

데이터에 대해 동시 접근이 허용된다면?

트랜잭션1은 만원을 빼고, 트랜잭션2는 만원을 입금하는 처리가 일어날 때, 의도했던 결과는 트랜잭션 1을 통해 0원, 2를 통해 만원의 결과가 나오길 바란다. 하지만 트랜잭션1이 끝나기 이전 값을 읽어 2만원의 결과가 나올 가능성이 있다.

 

이를 해결하기 위해, 트랜잭션을 점유하는 을 사용한다. 트랜잭션이 시작되면, 이용하고 있는 (ROW, TABLE)은 트랜잭션이 끝날때까지 점유한다.

트랜잭션1은 만원 상태에서 만원을 빼주고, 트랜잭션2는 0으로 바뀐 상태에서 만원을 추가하므로, 의도한 대로 동작한다.

DB는 격리 수준에 따라 트랜잭션이 실행되는 동안 각기 다른 Lock을 걸어 데이터를 보호한다. 격리 수준이 높아질 수록 더욱 강하게 Lock을 걸고, 트랜잭션을 마치면 Lock을 해제한다.

 

스프링이 지원하는 트랜잭션에 대한 자세한 내용은 이전 글 스프링 트랜잭션 참고

 

'우아한테크코스 > 테코톡 정리' 카테고리의 다른 글

JDK Dynamic Proxy vs CGLIB Proxy  (3) 2022.10.18
Spring AOP  (0) 2022.10.14
인덱스  (0) 2022.10.14
CI/CD와 무중단 배포  (0) 2022.10.09
스프링 트랜잭션  (0) 2022.10.02