| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- Kubernetes
- RDBMS
- 성능 최적화
- selector
- 데이터베이스
- helm
- kafka
- netty
- 트랜잭션
- redis
- prometheus
- SpringBoot
- Kotlin
- NIO
- mysql
- 동시성제어
- Java
- docker
- 백엔드개발
- 백엔드
- spring boot
- grafana
- monitoring
- jvm
- JPA
- webflux
- GitOps
- CloudNative
- DevOps
- 성능최적화
- Today
- Total
유성
RDBMS 격리 수준 본문
이 글은 신입 시절 처음 면접 준비를 할 때 CS를 공부하고 외웠지만, 시간이 지나면서 대부분을 잊어버려 이러한 학습 방식이 과연 의미가 있는지 고민하여 작성하게 되었습니다.
장기적인 관점에서는 개념을 단순히 외우는 것보다는 각 기술이 어떤 원리로 작동하는 지 알고 있어야 이를 개선하거나, 사용하는데 도움을 줄수있지 않을까 생각합니다.
RDBMS 구조
먼저 데이터가 RDBMS에 어떤 방식으로 저장되는지부터 살펴보겠습니다.
(각 RDBMS마다 구현 방식이 아예 다를 수 있지만 Normal한 RDBMS로 설명합니다.)
우리가 사용하는 RDBMS는 대체로 B+Tree 알고리즘을 사용해 저장합니다.

B+Tree 알고리즘이란 정렬된 트리 형태로, 말단 노드 간의 빠른 탐색을 위해 LinkedList 구조를 가지는 특징이 있습니다.
저장 구조
데이터 저장은 크게 파일(File), 페이지(Page), 레코드(Record) 라는 세 가지 요소로 나뉩니다.
1. 레코드
하나의 row 데이터를 의미하며, 기본 키(PK)를 포함합니다.

2. 페이지
B+Tree 구조에서의 노드(Node) 역할을 하며, 여러 개의 레코드(혹은 인덱스 엔트리)를 포함하는 RDBMS의 최소 데이터 저장 및 I/O 단위입니다.

3. 파일
여러개의 페이지가 모여 하나의 파일을 구성합니다.

페이지를 읽고 수정하는 방식
1. 데이터 읽기
데이터를 읽을 때는 주로 MVCC(Multi-Version Concurrency Control)라는 다중 버전 동시성 제어 기술을 활용합니다.
이는 각 트랜잭션이 데이터의 '스냅샷'으로 만들고 읽도록 합니다.
이 글에서는 스냅샷이라고 하겠습니다.
2. 데이터 수정
데이터 수정할 때는 메모리에 페이지 복사본을 올려 수정을 한 후 특정 시점에 디스크에 기록됩니다.
여기서는 각 수정에 대해서 버전으로 설명하겠습니다.
예를 들어 커밋된 데이터를 "트랜잭션 ID 72번" 버전이라고 한다면 이후 커밋되지 않은 수정에 대해서 "트랜잭션 ID 73번" 버전이라고 하겠습니다.
트랜잭션 격리 수준과 동시성 문제
트랜잭션의 격리 수준(Isolation Level)에 따라 발생 가능한 동시성 문제가 달라집니다.
아래 예시에서는 한명이 Select를 다른 한명이 Update/Insert/Delete를 동시에 수행하는 충돌 상황을 말합니다.
1. Read Uncommitted
가장 낮은 수준의 격리 수준으로, 커밋되지 않은 데이터도 읽을 수 있습니다.
즉, 메모리에서 수정 중인 마지막 버전의 데이터를 바로 읽기 때문에 커밋되지 않은 데이터를 읽을 수 있습니다.
2. Read Committed
커밋된 데이터만을 읽는 방식으로, 메모리에 있는 최종 커밋된 버전이나 디스크에서 데이터를 읽습니다.
상대방의 데이터가 커밋되는 즉시 읽기가 가능하며, 트랜잭션 내에서도 조회할 때마다 값이 변경될 수 있습니다.
3. Repeatable Read
트랜잭션 시작 시 생성한 스냅샷을 사용하여 데이터를 읽습니다. 따라서 트랜잭션이 진행되는 동안 데이터가 수정되어도 처음 생성한 스냅샷의 데이터를 계속 사용하므로 일관성을 유지합니다.
4. Serializable
락을 거는 방식을 많이 사용하며, 모든 트랜잭션을 직렬화된 순서로 보이게 함으로 동시성 문제를 제어합니다.
성능 오버헤드가 가장 큽니다.
격리 수준에 따른 문제점
1. Dirty Read
커밋되지 않은 데이터를 읽는 문제입니다.
이는 버전관리의 마지막 버전을 읽는 것으로 Read Uncommitted 수준에서 발생합니다.
2. Non-Repeatable Read
트랜잭션 내에서 같은 데이터를 여러 번 읽을 때마다 값이 달라지는 문제입니다.
이는 읽기 스냅샷을 사용하지 않을 때 발생할 수 있는 문제로 Read Uncommitted와 Read Committed 수준에서 발생합니다.
3. Phantom Read
트랜잭션 내에서 범위 조회 시 다른 트랜잭션의 INSERT로 인해 새로운 데이터가 나타나는 문제입니다.
Phantom Read는 Non-Repeatable Read와 비슷해 보일 수 있지만, 핵심은 "새로운 데이터의 등장(INSERT)"입니다.
Read Uncommitted와 Read Committed 수준에서는 당연히 발생 가능하며,
Repeatable Read 수준에서도 범위 조회 시 발생할 수 있습니다.
스냅샷은 동일한 결과를 표출한다고 했는데?
Repeatable Read 수준은 이미 존재하는 레코드의 변경에 대해서는 스냅샷을 통해 일관성을 보장하지만,
범위 조회 시 스냅샷으로 관리하지 않는 부분에서 데이터가 생성되었다고 한다면 조회가 가능한 데이터가 되기 때문입니다.
이는 트랜잭션 시작 시점의 스냅샷이 새로운 레코드의 삽입 영역까지 예측하여 잠그지는 못하기 때문입니다.
모든 DB 들이 위와 같이 동작을 하는것은 아니지만 CS에 관련되어 이해를 위한 글입니다.
'Spring Data' 카테고리의 다른 글
| JPA의 진짜 설계 의도: 왜 N+1 문제를 방치하였나? (4) | 2025.07.26 |
|---|---|
| [JPA] fetchJoin 과 Paging 처리의 한계 및 해결 방안 (0) | 2025.03.12 |
| 예외 발생 시 롤백 방지하기: Spring에서 독립적인 트랜잭션 설정 방법 (3) | 2024.09.07 |
| JPA와 Hibernate의 차이점은 무엇인가요? (0) | 2023.09.02 |
| ORM이란 무엇인가요? (0) | 2023.09.02 |