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
시나리오 분석: 아래 타임라인을 통해 두 트랜잭션이 어떻게 상호작용하는지 자세히 살펴보자.
171
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원)과 달라 업데이트는 실패한다.
172
+
* T1: 트랜잭션 A(5,000원 결제)와 B(3,000원 결제)가 거의 동시에 시작된다. 트랜잭션 B는 잔액 `10,000원`을 조회하고, 이 시점에 **B를 위한 데이터 스냅샷(Snapshot)이 생성된다.**
173
+
174
+
* T2 ~ T3: 트랜잭션 A가 먼저 DB Lock을 획득하여 결제를 처리한다. 트랜잭션 B는 동일한 데이터에 접근하려다 Lock을 얻지 못하고 **대기(Blocked) 상태**가 된다.
175
+
176
+
* T4: 트랜잭션 A가 처리를 마치고 `5,000원`이 차감된 잔액을 **커밋(Commit)**한다. DB의 실제 잔액은 `5,000원`이 되고 Lock은 해제된다. 대기하던 트랜잭션 B가 드디어 Lock을 획득하고 깨어난다.
177
+
178
+
* T5: 로직을 재개한 트랜잭션 B가 비즈니스 로직을 위해 잔액을 참조한다. 이때 B는 DB의 최신 값(5,000원)을 보는 것이 아니라, **T1에서 생성한 자신만의 스냅샷**을 일관되게 참조한다. 따라서 B에게 잔액은 여전히 `10,000원`으로 보인다. 이것이 바로 `REPEATABLE READ`의 핵심 동작이다.
179
+
180
+
* T6 ~ T7: 트랜잭션 B는 10,000원을 기준으로 3,000원 결제를 처리한 후, `UPDATE ... WHERE balance = 10000` 쿼리를 실행한다. 하지만 T4 시점에 A가 커밋한 DB의 실제 잔액은 5,000원이므로 `WHERE` 조건이 불일치하여 **최종 업데이트는 실패**한다.
176
181
177
182
이처럼 `REPEATABLE READ`는 **'트랜잭션 내의 일관성'** 을 위해 **'데이터의 최신성'** 을 희생하는 전략을 사용한다.
178
183
179
184
이 특징을 이해해야 동시성 이슈가 발생했을 때 정확한 원인을 파악하고 `READ COMMITTED`와 같은 다른 격리 수준을 대안으로 고려할 수 있다.
180
185
186
+
이처럼 `REPEATABLE READ`는 트랜잭션의 일관성을 보장하기 위해 **자신이 시작된 이후에 커밋된 변경 사항은 반영하지 않는다.**
187
+
188
+
이 특징을 이해해야 동시성 이슈가 발생했을 때 정확한 원인을 파악하고 `READ COMMITTED`와 같은 다른 격리 수준을 대안으로 고려할 수 있다.
0 commit comments