23.12.07 트랜잭션과 무결성
1. 트랜잭션
- DB에서 하나의 논리적 기능을 수행하기 위한 작업의 단위
- DB에 접근하는 방법은 쿼리이므로, 여러개의 쿼리들을 하나로 묶는 단위
- 이에 대한 특징은 원자성, 일관성, 독립성, 지속성이 있고 이를 한꺼번에 ACID 특징이라함
1) 원자성 (Atomicity)
- 트랜잭션과 관련된 일이 모두 수행되었거나되지 않았거나를 보장하는 것이 특징
- 트랜잭션을 커밋했는데 문제가 발생하여 롤백하는 경우 이후에 모두 수행되지 않음을 보장함
- 예) 1000만원을 가진 홍철이가 0원을 가진 규영이에게 500만원을 이체한다고 가정
홍철이는 500만원, 규영이는 500만원을 가지게됨 해당 결과는 아래와 같은 operation 단위들로 이루어진
과정을 거친다
a) 홍철이의 잔고 조회
b) 홍철에게서 500만원을 뺌
c) 슈영이에게 500만원을 넣는다
여기서 1 ~ 3 의 operation 중 DB 사용자는 이 세가지의 과정을 볼 수도 참여할 수도 없다.
다만, 이과정이 모두 끝난 이후의 상황인 홍철 500만원, 규영 500만원인 상황만 본다
만약, 이 작업을 취소한다고 했을 때 홍철이는 다시 1000만원, 규영이는 0원을 가져야 한다
일부 operation만 적용된 홍철이는 500만원, 규영이는 0원이 되지않는 것을 의미한다. 따라서 All or Norhing
- 트랜잭션 단위로 여러 로직을 묶을 때 외부 API를 호출하는 것이 있으면 안됨
- 외부 API 호출이 있다면 롤백이 일어났을 때 어떻게 해야 할 것인지에 대한 해결방법이 필요하고,
트랜잭션 전파를 신경써서 관리해야함
(1) 커밋
- 여러 쿼리가 성공적으로 처리되었다고 확정하는 명령어
- 트랜잭션 단위로 수행되며 변경된 내용이 모두 영구적으로 저장됨
- 커밋이 수행되었다를 하나의 트랜잭션이 성공적으로 수행되었다고 함
- update, inseret, delete의 쿼리가 하나의 트랜잭션 단위로 수행되고 이후에 DB에 영구 저장
(2) 롤백
- 에러나 여러 이슈 때문에 트랜잭션 전으로 돌리는 것
- 트랜잭션으로 처리한 하나의 묶음 과정을 일어나기 전으로 돌리는 일(취소)
- 커밋과 롤백 덕에 데이터 무결성이 보장됨 또한, 데이터 변경 전에 변경 사항을 쉽게 확인 할 수 있고
해당 작업 그룹화 가능
(3) 트랜잭션 전파
- 트랜잭션을 수행할 때 커넥션 단위로 수행가기 때문에 커넥션 객체를 넘겨서 수행해야함
- 이를 매번 넘겨주기가 어렵고, 귀찮음
- 이를 넘겨서 수행하지 않고 여러 트랜잭션 관련 메소드의 호출을 하나의 트랜잭션에 묶이도록 하는 것을
트랜잭션 전파라 함
- Spring 프레임워크에서는 아래 코드 처럼 @Transactional 애너테이션을 통해 여러 쿼리 관련 코드들을
하나의 트랜잭션으로 처리함
@Service
@Transactional
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
2) 일관성 (Consistency)
- 허용된 방식으로만 데이터를 변경해야 하는 것
- DB에 기록된 모든 데이터는 여러가지조건, 규칙에 따라 유효함을 가져야 한다
- 예) 홍철이는 1000만원이 있고 범석이는 0원이 있다고 가정할 때, 범석이가 필자한테 500만원을 입금 할 수 없다
3) 격리성 (Isolation)
- 트랜잭션 수행 시 서로 끼어들지 못하는 것
- 복수의 병렬 트랜잭션은 서로 격리되어 마치 순차적으로 실행되는 것처럼 작동되어야 함
- DB는 여러가지 사용자가 같은 데이터에 접근할 수 있어야 함
- 순차적으로 하면 쉽게 되겠지만 성능이 나쁨
- 여러개의 격리 수준으로 나누어 격리성 보장
- 격리 수준은 SERIALIZABLE, REPEATABLE_READ, READ_COMMITTED, READ_UNCOMMITTED 가 있다
- 위로 갈수록 동시성이 강해지지만 격리성은 약해짐
(1) 격리수준에 따라 발생하는 현상
A) 팬텀 리드
- 한 트랜잭션 내에서 동일한 쿼리를 보냈을 때 해당 조회결과가 다른 경우
- 예) 사용자 A가 회원 테이블에서 age 가 12이상인 회원들을 조회하는 쿼리를 보낼 때, 세개의 테이블이
조회한다고 하면, 그 다음 사용자 B가 age가 15인 회원 레코드를 사입하면 그다음 세개가 아닌
네개의 테이블이 조회됨
B) 반복 가능하지 않은 조회 (non-repeatable read)
- 한 트랜잭션 내의 같은 행에 두번 이상 조회가 발생했는데 그 값이 다른 경우
- 예) 사용자 A가 큰돌의 보석개수가 100개 라는 값을 가진 데이터였는데 그 이후 사용자 B가
그 값을 1로 변경해서 커밋했다고 하면 사용자 a는 100이 아닌 1을 읽게됨
- 팬텀리드와 다른점은 반복 가능하지 않은 조회는 행값이 달라질 수도 있는데
팬텀 리드는 다른 행이 선택될 수도 있다는 것을 의미
C) 더티 리드
- 반복 가능하지 않은 조회와 유사하며 한 트랜잭션이 실행 중일 때 다른트랜잭션에 의해 수정되었지만
아직 커밋되지 않은 행의 데이터를 읽을 수 있을 때 발생
- 예) 사용자 A가 큰돌의 보석 개수 100을 1로 변경한 내용이 커밋되지 않은 상태라도 그 이후 사용자 B가
조회한 결과가 1로 나오는 경우
(2) 격리수준
A) SERIALIZALBLE
- 트랜잭션을 순차적으로 진행시키는 것
- 여러 트랜잭션이 동시에 같은 행에 접근 할 수 없다
- 매우 엄격한 수준으로 해당 행에 대해 격리시키고 , 이후 트랜잭션이 이 행에 대해 일어난다면 기다려야 함
- 교착 상태가 일어날 확률도 많고 가장 성능이 떨어지는 격리 수준임
B) REPEATABLE_READ
- 하나의 트랜잭션이 수정한 행을 다른 트랜잭션이 수정할 수 없도록 막아주지만 새로운 행을 추가하는것은
막지 않음
- 이후에 추가된 행이 발견될 수 있다
C) READ_COMMITTED
- 가장 많이 사용되는 격리 수준이며 MySQL8.0, PostgreSQL, SQL Server, 오라클에서 기본값으로 설정됨
- 트랜잭션이 커밋하지 않은 정보는 읽을 수 없다
- 커밋 완료된 데이터에 대해서만 조회를 허용
-다만, 어떤 트랜잭션이 접근한 행을 다른 트랜잭션이 수정할 수 있다
예) 트랜잭션 A가 수정한 행을 트랜잭션 B가 수정할 수 있다. 이 때문에 트랜잭션 A가 같은 행을 다시 읽을 때
다른 내용이 발견 될 수 있다
D) READ_UNCOMMITTED
- 가장 낮은 격리 수준
- 하나의 트랜잭션이 커밋되기 이전에 다른 트랜잭션에 노출되는 문제가 있으나 가장 빠르다
- 데이터 무결성을 위해 되도록이면 사용하지않는 것이 이상적
- 몇몇 행이 제대로 조회되지 않더라도 괜찮은 거대한 양의 데이터를 어림잡아 집계하는 데에 사용하면 좋다
4) 지속성 (Durability)
- 성공적으로 수행된 트랜잭션은 영원히 반영되어야 함
- DB에 시스템 장애가 발생해도 원래 상태로 복구하는 회복 기능이 있어야 함
- DB는 이를 위해 체크섬, 저널링, 롤백 등 기능을 제공
* 체크섬 : 중복 검사의 한 형태로, 오류 정정을 통해 송신된 자료의 무결성을 보호하는 단순한 방법
* 저널링 : 파일 시스템 또는 DB 시스템에 변경 사항을 반영 (commit) 하기 전에 로깅하는 것, 트랜잭션 등
변경 사항에 대한 로그를 남기는 것
2. 무결성
- 데이터의 정확성, 일관성, 유효성을 유지하는 것
- DB에 저장된 데이터 값고 그 값에 해당하는 현실 세계의 실제 값이 일치 하는지에 대한 신뢰가 생김
- 무결성의 종류는 아래와 같다