1. 트랜잭션 (Transaction)
데이터베이스의 상태를 변화시키기 위해 수행하는 작업단위
(1) 상태 변화
데이터베이스의 상태를 변화시킨 다는 것은
쿼리문을 가지고 데이터베이스에 접근해 수정하고, 삭제하는 것을 말한다.
백엔드 개발을 하면서 crud 작업을 수행하며 이와 같은 작업들이 모두 데이터베이스의 상태를 변화시키게 된다.
(2) 작업 단위
작업단위는 프로젝트별, 상황별 정의하기 나름이다.
작업단위 예시
ex) 트랜잭션 : 오늘 날씨에 대한 일기 작성하기
오늘 일기 작성하기라는 작업 단위를 두었다면, 이 하나의 작업단위 내에는
(1) 오늘 날씨 데이터를 가져오기, (2) 일기를 DB에 저장하기 이렇게 두 가지 단위로 나눌 수 있다.
이 (1) (2) 를 수행한 것이 하나의 데이터베이스 상태를 변화시키기 위해 수행하는 작업단위가 된다!
하지만 (1) 오늘 날씨 데이터 가져오기를 수행 중에
날씨 데이터를 가져오지 못했거나, 반대로 일기를 DB에 저장하는 과정에서 문제가 생길 수도 있다.
(1)이나 (2) 작업 중에 문제가 생긴다면 롤백을 시켜버리는 등으로 상황을 해결할 수 있다.
2. 트랜잭션의 4가지 속성
- 원자성 (Atomicity)
트랜잭션이 DB에 모두 반영이 되거나, 모두 반영되지 않는다.
- 일관성 (Consistency)
트랜잭션에 작업 처리 결과는 항상 일관적이어야 한다.
- 독립성 (Isolation)
독립적으로 하나의 트랜잭션이 수행되고, 다음 트랜잭션이 수행되어야 한다.
- 지속성 (Durability)
트랜잭션이 성공적으로 완료가 되었다면, 트랜잭션이 DB의 상태를 변화시켰을 것이다.
변화된 상태가 쭉 지속이 되어야 한다. (영구적으로 반영이 되어야 한다.)
3. 트랜잭션이 수행되고 나면?
트랜잭션이 수행되고 나면 가질 수 있는 연산이 2가지가 있다.
(1) 커밋 (Commit)
트랜잭션이 성공적으로 수행되었을 때, 마무리 작업으로 커밋을 하게 된다.
(2) 롤백 (Rollback)
트랜잭션 전체를 하다가 그 과정에서 예외상황이나 문제가 생겼을 때, 마무리 작업으로 트랜잭션 전체를 롤백해버린다.
하나의 트랜잭션 안에 5개의 세부작업들이 있다고 가정하면,
- 이 세부작업들 중에 5개가 성공적으로 완료되었을 때는 마지막 연산인 커밋이 되어서 이 5개의 세부작업들을 DB에 성공적으로 반영을 시킨다.
- 이 세부작업들중에 3개만 성공하고, 2개가 실패했을 때는 마지막 연산으로 롤백이 되어서 동작했던 3개의 작업을 취소해 버린다. (이렇게 롤백을 하면, 트랜잭션 전체가 작업되기 이전에 DB 상태로 돌아간다!)
4. 여러 트랜잭션이 경쟁하면 생기는 문제
이 부분은 실제로 면접에서도 많이 물어보는 부분이다.
Q. 여러 트랜잭션이 경쟁을 하면 어떤 문제가 생길 수 있나요?
Q. 그리고 그 문제를 어떻게 해결해 본 경험이 있나요?
특정 트랜잭션 하나가 처리 중이고, 아직 완료랑 커밋되기 전인 상태에서
또 다른 트랜잭션이 같은 DB에 접근을 한 경우에는 이 문제를 어떻게 해결하면 좋을까?
위 상황처럼여러 트랜잭션이 동시에 시행되는 상황에는 트랜잭션을 어떻게 설계하면 좋을지 고려해야 한다!
(1) Dirty Read
트랜잭션 A : Diary 테이블의 3번째 row 수정 중
트랜잭션 B : Diary 테이블의 3번째 row 조회하려고 함
위 예시가 Dirty Read 문제가 생길 수 있는 상황이다.
트랜잭션 A가 3번째 row에 1이라는 숫자를 2로 수정했다. 그리고 커밋을 하기 전인 상황이다.
그 상황에서 트랜잭션 B가 테이블의 3번째 row를 조회하려고 하면 2를 조회하게 된다.
트랜잭션 A가 수정을 하고 커밋까지 완료했으면 아무 문제도 없겠지만,
트랜잭션 A가 커밋되지 않고 롤백 됐다면 1이라는 값은 2로 바뀐 게 아니라 1로 남게 된다.
이렇게 되면, 트랜잭션 B는 존재하지 않는 2라는 값을 조회하게 된다.
정리하자면, Dirty Read는 트랜잭션이 어떤 값을 수정하고 있는데 다른 트랜잭션이 접근이 가능하도록 열려있어서 생기는 문제이다.
예시는 간단하게 1에서 2를 바꾸는 정도였지만.. 은행의 계좌정보라던지, 잔액과 연관이 되어있는데 Dirty Read 문제가 발생한다면 정말 큰일이 날 수도..!
(2) Non-Repeatable Read
트랜잭션 A : Diary 테이블의 3번째 row 조회 * 2
트랜잭션 B : Diary 테이블의 3번째 row 수정 후 커밋
위 예시가 Non-Repeatable Read 문제가 생길 수 있는 상황이다.
Non-Repeatable Read는 Dirty Read 랑 비슷하면서도 다른 문제이다.
하나의 트랜잭션 A가 3번째 row를 조회하는 작업이 두 번 있고,
이 트랜잭션 A는 트랜잭션 안에 Diary 테이블의 3번째 row를 한 번 조회하고, 또 한 번 더 조회하는 과정이 하나의 트랜잭션으로 쌓여있다.
트랜잭션 A가 쿼리를 날리고 다음 쿼리를 날리려는 사이에 트랜잭션 B가 3번째 row를 수정하고 커밋까지 해버린 상황이다. 트랜잭션 A 입장에서는 첫 번째 조회했을 때 값이랑 두 번째 조회했을 때 값이 달라지게 된다.
즉, 트랜잭션 4가지 속성 중에 트랜잭션 작업 결과는 일관적이어야 한다는일관성을 만족시키지 못한 케이스가 된다.
정리하자면, Non-Repeatable Read 는 여러 트랜잭션이 경쟁해서 트랜잭션의 일관성을 해치게 되는 문제이다.
(3) Phantom Read
트랜잭션 A : Diary 테이블의 0~4번째 row 조회 * 2
트랜잭션 B : Diary 테이블의 3번째 row 수정 후 커밋
위 예시가 Phantom Read 문제가 생길 수 있는 상황이다.
Phantom Read 는 Non-Repeatable Read 문제와 매우 유사하다.
트랜잭션 A가 Non-Repeatable Read 에 설명한 예시 문제 상황과 쿼리를 두 번 날리는 것은 동일하다.
Non-Repeatable Read 문제와 차이점은 트랜잭션 A가 3번째 row만 조회하는 게 아니라,
(0 ~ 4번째 row) 일정 구역의 데이터를 조회하는 것이다.
그리고 그 데이터를 조회할 때 범위 안에 있는 값이 다른 트랜잭션 B에 의해서 변경되는 상황이다.
즉, 트랜잭션의 일관성이 침해된 케이스이다.
정리하자면, Phantom Read 는 특정 범위 내에서 값을 두 트랜잭션이 경쟁했을 때 생기는 문제이다.
# 트랜잭션 경쟁 3가지 문제점 요약
- Dirty Read
트랜잭션이 어떤 값을 수정하고 있는데 다른 트랜잭션이 접근이 가능하도록 열려있어서 생기는 문제 (원자성 관련)
- Non-Repeatable Read
특정 값을 두 트랜잭션이 경쟁했을 때 생기는 문제 (일관성 관련)
- Phantom Read
특정 범위 내에서 값을 두 트랜잭션이 경쟁했을 때 생기는 문제 (일관성 관련)