You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: _posts/2023-02-20-DB-Transaction-Isolation-Level.md
+52-1Lines changed: 52 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -147,6 +147,38 @@ author: devFancy
147
147
148
148
* Phantom Read
149
149
150
+
#### REPEATABLE READ와 스냅샷
151
+
152
+
> 2025.07.23 업데이트
153
+
154
+
앞선 설명만 보면 한 가지 의문이 생길 수 있다.
155
+
156
+
"REPEATABLE READ도 커밋된 데이터를 읽는다고 했는데, 왜 다른 트랜잭션이 커밋한 최신 데이터를 보지 못하는 걸까?"
157
+
158
+
앞선 설명만 보면 한 가지 의문이 생길 수 있습니다. "REPEATABLE READ도 커밋된 데이터를 읽는다고 했는데, 왜 다른 트랜잭션이 커밋한 최신 데이터를 보지 못하는 걸까?"
159
+
160
+
결론부터 말하면 **`REPEATABLE READ`는 트랜잭션 내 첫 `SELECT` 시점의 '스냅샷(Snapshot)'을 만들어, 트랜잭션이 끝날 때까지 그 스냅샷의 데이터만 일관되게 보여주기 때문**이다. 이는 MySQL의 **MVCC(Multi-Version Concurrency Control)** 와 관련된 핵심 동작 방식이다.
161
+
162
+
다른 트랜잭션이 데이터를 변경하고 커밋해도, 현재 트랜잭션은 **'반복 가능한 읽기'를 보장하기 위해 의도적으로 최신 변경 사항을 무시**하고 자신만의 스냅샷을 참조한다.
163
+
164
+
아래 그림은 동시 결제 요청 시 스냅샷으로 인해 문제가 발생하는 과정을 보여준다.
165
+
166
+
> REPEATABLE READ의 스냅샷 동작 원리
167
+
168
+
![]()
169
+
170
+
시나리오 분석:
171
+
172
+
1. 스냅샷 생성: 트랜잭션 B가 처음 잔액을 조회(`SELECT`)하는 순간, InnoDB는 해당 시점의 데이터 복사본, 즉 **스냅샷**을 만듭니다. 이 스냅샷에는 잔액이 `10,000원`으로 기록된다.
173
+
2. 데이터 변경: 트랜잭션 A가 먼저 Lock을 얻어 결제를 처리하고, 실제 DB의 잔액을 `5,000원`으로 변경 후 커밋한다.
174
+
3. 스냅샷 참조: 트랜잭션 B는 A의 커밋 이후 로직을 재개하지만, DB의 최신 데이터를 보지 않고 **자신이 만들어 둔 스냅샷을 계속 참조한**다. 따라서 B에게 잔액은 여전히 `10,000원` 이다.
175
+
4. 업데이트 실패: 결국 B는 `UPDATE ... WHERE balance = 10000` 쿼리를 실행하게 되고, 실제 DB 값(5,000원)과 달라 업데이트는 실패한다.
176
+
177
+
이처럼 `REPEATABLE READ`는 **'트랜잭션 내의 일관성'** 을 위해 **'데이터의 최신성'** 을 희생하는 전략을 사용한다.
178
+
179
+
이 특징을 이해해야 동시성 이슈가 발생했을 때 정확한 원인을 파악하고 `READ COMMITTED`와 같은 다른 격리 수준을 대안으로 고려할 수 있다.
180
+
181
+
150
182
### SERIALIZABLE
151
183
152
184
* 말 그대로 트랜잭션을 순차적으로 진행시키는 것을 말한다.(직렬화)
@@ -161,6 +193,23 @@ author: devFancy
161
193
162
194
* 발생할 수 있는 문제 X
163
195
196
+
### 어떤 격리 수준을 선택해야 할까?
197
+
198
+
지금까지의 내용을 보면 `REPEATABLE READ`의 스냅샷 동작 방식이 문제를 일으켰으니, 무조건 `READ COMMITTED`를 써야 할 것처럼 보일 수 있다. **하지만 항상 그런 것은 아니다.**
199
+
200
+
**`REPEATABLE READ`가 유리한 경우도 있다.**
201
+
예를 들어, 사용자가 쇼핑몰에서 1,000원짜리 상품을 보고 결제를 진행한다고 상상해 보자. 만약 격리 수준이 `READ COMMITTED`라면, 사용자가 결제하는 그 짧은 순간에 운영자가 상품 가격을 1,500원으로 변경하고 커밋할 수 있다. 이 경우 사용자는 1,000원을 예상했지만 실제로는 1,500원이 결제되는 혼란스러운 상황을 겪게 된다.
202
+
203
+
반면, `REPEATABLE READ` 환경에서는 사용자의 결제 트랜잭션이 시작될 때 상품 가격(1,000원)이 스냅샷으로 저장된다. 중간에 가격이 1,500원으로 변경되더라도 사용자의 트랜잭션 내에서는 일관되게 1,000원으로 처리되어 예측 가능한 경험을 제공한다.
204
+
205
+
결국 정답은 없다.
206
+
격리 수준의 선택은 '데이터의 최신성'과 **'트랜잭션 내의 일관성(논리적 정합성)'** 사이의 트레이드오프이다.
207
+
208
+
-`READ COMMITTED`: 다른 트랜잭션의 변경 사항을 빠르게 반영해야 하는 금융 정보 조회 등 **데이터 최신성**이 중요할 때 유리하다. (ex: 계좌 잔액)
209
+
-`REPEATABLE READ`: 하나의 비즈니스 로직이 진행되는 동안 데이터의 일관성을 유지하는 것이 더 중요할 때 유리하다. (ex: 결제 시점의 상품 가격, 재고 수량 등)
210
+
211
+
따라서 개발자는 RDBMS(Oracle, MySQL 등)별 기본 격리 수준을 인지하고, 서비스의 비즈니스 정책과 요구사항을 면밀히 분석하여 각 트랜잭션에 가장 적합한 격리 수준을 적용해야 한다.
*[트랜잭션 - Isolation level(격리 수준)과 이상 현상](https://github.com/devfancy/2023-CS-Study/blob/main/DB/db_transaction_isolation_level.md)
231
+
*[트랜잭션 - Isolation level(격리 수준)과 이상 현상](https://github.com/devfancy/2023-CS-Study/blob/main/DB/db_transaction_isolation_level.md)
232
+
233
+
*[[네이버페이] 실무에서 만나는 DB isolation level](https://medium.com/naverfinancial/%EC%8B%A4%EB%AC%B4%EC%97%90%EC%84%9C-%EB%A7%8C%EB%82%98%EB%8A%94-db-isolation-level-e94a904bbf9d)
0 commit comments