일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- posix
- jpa n+1 문제
- heapq
- python
- 스케줄링
- 힙트리
- 강화학습
- 엔티티 그래프
- MSA
- 프로세스
- JPA
- 최소힙
- 연결리스트 종류
- 백준장학금
- 최대 힙
- 자료구조
- 멀티프로세서
- 이분탐색이란
- JVM
- 점근적 표기법
- 운영체제
- 연결리스트
- SpringSecurity
- 백준 장학금
- spring
- 알고리즘
- AVL트리
- Kruskal
- 완전이진트리
- HTTP
- Today
- Total
KKanging
관계형 DB의 트랜잭션(Transaction) 본문
주의
해당 내용은 포괄적인 RDB 에서의 트랜잭션에 대한 설명도 있지만 필자가 MySQL 에 대한 지식을 위주로 설명하니 주의!
서론
RDB 를 처음 공부했을 때 트랜잭션은 ACID을 지원해준다! 트랜잭션은 관계형 데이터베이스의 특징 중 하나이다! 라는 식으로 공부했다.
물론 맞는말이지만 원리는 이해하지 않은채 “아! 그렇구나 트랜잭션은 명령을 고립시켜주는 장치이고 RDB의 특징이구나!” 로만 외우다 보니 다른 NoSQL 같은 DB를 사용했을 때 트랜잭션을 지원해주지 않거나 트랜잭션을 지원해준다 했을 때 RDB의 트랜잭션과 혼동이 오곤 했다.
RDB, 그중에서 MySQL에서 Transaction 기능을 어떻게 구현했고 어떻게 ACID 를 지원해주는지 학습하면서 트랜잭션이란 장치에 대해 딥다이브를 해보자
관계형 DB의 트랜잭션
RDB 트랜잭션의 특징
- Atomicity(원자성)
- Consistency(일관성)
- Isolation(격리성)
- Duration(영속성)
위 개념들은 트랜잭션의 특징들이다. 해당 특징들은 블로그나 지피티를 통해 조금만 찾아봐도 알 수 있는 개념들이다.
이제 트랜잭션을 하나하나 무엇이고 어떻게 구현되었는지 보자
Atomicity(원자성)
“트랜잭션의 작업이 부분적으로 실행되거나 중단되지 않는 것을 보장한다.”

만약 위처럼 구성된 트랜잭션이 있다고 가정하자
커밋이 완료되고 트랜잭션이 끝나면 해당 레코드의 count 어트리뷰트의 값은 20이 되어야 한다.
다음을 가정해보자

트랜잭션이 끝나기 전에 특이 사유로 롤백이 되었다. 하지만 이미 데이터를 수정했는데 어떻게 이전 상태로 되돌릴 수 있을까?
쉬운 생각으론 수정하기 전 데이터 값을 어딘가에 저장해놓고 롤백된다면 되돌리는 것을 생각할 것이다.
실제로 MySQL 그리고 대다수의 RDB들을 다음 방법을 사용한다

위 그림처럼 데이터를 수정할 때 수정하기 전 데이터도 같이 저장한다(MySQL의 InnoDB는 Undo 로그라는 영역에 저장한다)
따라서 만약 롤백이 실행된다면 다음과 같이 원본 데이터는 수정하기 전 데이터 버전으로 되돌린다.

이렇게 MySQL 은 Undo 로그라는 트랜잭션이 끝나기 전까기 트랜잭션이 수정하기 이전의 데이터를 보관하는 영역을 사용하여 트랜잭션의 원자성을 보장한다.
Consistency(일관성)
일관성(Consistency)은 트랜잭션이 완료된 후 데이터베이스가 일관된 상태를 유지해야 한다는 속성이다.
MySQL을 포함한 RDB들은 개체 무결성, 참조 무결성과 같은 제약조건(Constraints)으로 이를 보장한다.
Isolation(격리성)
격리성(Isolation)은 하나의 트랜잭션이 다른 트랜잭션에 의해 중간 상태가 영향을 받지 않도록 보장하는 속성이다. 만약 하나의 트랜잭션이 다른 트랜잭션에 의해 영향을 받는다면 어떻게 될까?

위 상황을 보자 A트랜잭션이 id 가 1 인 name 을 park 로 변경하고 아직 commit 을 호출하지 않았다.
이때 B 트랜잭션이 데이터를 읽었고 park 라는 이름을 읽었다.
이후 A는 커밋을 하지않고 롤백을 했다. 이 때 B는 실재로는 적용되지 않은 없는 데이터를 읽은 샘이다.
이러한 다른 트랜잭션 간에 격리 문제를 Dirty Read 라고 부른다.
컴퓨터 공학적 지식이 있는 사람들은 “베타 락을 사용하여 읽지 못하고 쓰지도 못하게 하면 이러한 문제가 생기지 않을 것” 이라고 유추할 수 있다. 물론 맞는 말이지만, 현대의 트래픽의 상승과 함께 현대 WAS들이 요구하는 DB 처리량에 제약을 주게 된다.
이를 해결하기 위해 트랜잭션의 격리수준을 다음과 같이 나누고 락 없는 격리된 읽기를 가능하게 하는 MVCC 라는 구조를 도입했다
- READ UNCOMMITTED: 다른 트랜잭션의 커밋되지 않은 데이터를 읽을 수 있습니다. 가장 낮은 수준.
- READ COMMITTED: 다른 트랜잭션이 커밋한 데이터만 읽을 수 있습니다.
- REPEATABLE READ: 트랜잭션 내에서 한 번 읽은 데이터는 다른 트랜잭션에서 변경되더라도 계속 같은 값을 읽습니다.
- SERIALIZABLE: 트랜잭션을 직렬화하여 실행합니다. 가장 높은 수준의 격리성.
위 예제는 READ UNCOMMITTED 수준의 격리 수준(Isolation Level)이다.
조금 센스있는 사람들은 우리가 원자성을 딥다이브 하면서 배운 Undo 로그 같은 영역을 이용하면 되지 않을까라고 추축할 수 있는데, Undo 로그 영역을 사용하여 MySQL은 READ COMMITTED 를 구현한다.

위에서 볼 수 있듯이 아까와 같은 예제인데 이번에 B 트랜잭션은 kang을 읽었다 이게 가능한 이유는 Undo 로그에 는 커밋된 데이터들이 존재하기 때문이다. 따라서 커밋된 데이터만 읽도록(READ COMMITTED) 를 Undo 로그로 구현했다.
다 해결된게 아니다 다음과 같은 상황이 발생할 수 있다.

B트랜잭션의 동작은 2번을 읽는데, A 트랜잭션이 그 사이에 데이터를 수정하고 commit 을 했다. 따라서 커밋된 데이터를 B 트랜잭션은 2번째 읽기 동작에서 park 을 읽게 된다.
이러한 문제를 Non-Repeatable Read 라고 부르면 같은 트랜잭션에서 반복적으로 데이터를 읽으면 변경된 데이터일 수 있다는 의미이다.
이를 해결하기 위해 MySQL 은 transaction id 를 사용하여 REPEATABLE READ 격리 수준을 구현한다.

트랜잭션이 시작될 때 tx id 를 발급 받는다.
어느 트랜잭션이 데이터를 수정하면 undo 로그에 데이터가 쌓인다. 이때 REPEATABLE READ 격리 수준의 MySQL은 undo 로그에 해당 데이터를 수정한 트랜잭션의 tx id를 같이 기입한다.
다른 트랜잭션이 데이터를 읽을 때 자기 tx id 보다 작은 undo 로그 데이터를 읽고 undo 로그 데이터는 자기를 읽을 수 있는 transaction 이 있으면 삭제되지 않는다.

마지막 문제는 B 트랜잭션 같이 범위로 읽는 행위를 할 경우 없던 데이터(Phantom READ) 가 생기는 경우다.
이 문제는 tx id 로도 해결 못한다. 이유는 undo 로그가 쌓이지 않고 범위에 데이터가 생기는 경우이기 때문이다.
이런 경우에는 SERIALIZABLE 격리 수준으로 올리는 방법인데, 해당 격리수준은 트랜잭션이 시작할 때 락을 점유하는 방법으로 사실상 MVCC 가 아닌 락을 사용하는 방법이다.
MySQL에서는 Next key lock 을 통해 Repeatable Read 수준에서도 이를 방지할 수 있는데 자세한 내용은 공식문서를 찾아보자
MySQL을 포함한 RDB들은 위와 같은 방법으로 트랜잭션 간에 격리성을 보장한다.
위에서 설명한 MVCC 격리수준은 개발자가 조정할 수 있으며 MVCC를 사용했다고 하는 것은 Read committed ,Repeatable Read 두 수준만을 칭하는 사람도 있다.
Duration(영속성)
지속성(Durability)은 트랜잭션이 성공적으로 커밋된 후에는 어떤 시스템 오류가 발생해도 그 변경 사항이 영구적으로 데이터베이스에 반영되도록 보장하는 속성이다.
현대 DB 들은 바로 디스크에 쓰는 행위가 무겁고 느리기 때문에 Dirty Write 지연된 쓰기를 적용한다.
MySQL 의 InnoDB 같은 경우에는 버퍼풀이라는 메모리 영역에 읽은 데이터와 쓰기 작업을 한 데이터를 보관하여 디스크 I/O 를 최소화한다.
하지만 만약 특정 트랜잭션이 쓰기작업을 하고 MySQL이 종료됐다면 데이터는 영속화하여 저장되지 않을 수도 있지 않나라고 생각할 수 있다.
MySQL은 리두 로그라는 영역으로 아직 디스크에 반영안된 더티 페이지(데이터)를 추적하여 변경 작업에 대한 로그를 비교적 적은 비용으로 디스크에 남기며 만약 MySQL 서버가 다운되더라도 리두로그를 통해 데이터를 복구할 수 있다.
'백엔드 > DB' 카테고리의 다른 글
[데이터베이스] InnoDB 스토리지 엔진 아키텍처 딥다이브 (3) | 2024.10.17 |
---|---|
[데이터베이스] MySQL 아키텍처 (2) | 2024.10.17 |
[데이터베이스] 동시성 제어? 락? 2PL? 그게 뭔데 (0) | 2024.10.10 |