DB

[DB] MVCC

개바리여리 2025. 8. 25. 00:19

기존 concurrency control 은 아래와 같이 락 획득이 제한되어있었다.

  read write
read o  x
write x x

 

mvcc(multiversion concurrency control)같은 데이터에 대하여 여러 트랜잭션을 아래와 같이 허용하여 작업을 하게 해준다.

  read write
read o  o
write o x

 

write - write 를제외하고는 블로킹이 되지 않게 되고,  동시에 처리 할 수 있는 트랜잭션 처리량이 증가하게 된다.

 

 

상황가정

1. x=10 이였을때 T2(트랜잭션2) 가 write (x=50) 을획득 

2. T1이 read(x) 로 10을 읽음

3. T2가 commit

4. read(x) 시에는 t1은 어떤한 값을 읽게되는가? (트랜잭션 격리 레벨에 따라 달라진다.)

 

 

4-1. 격리 레벨 : read commited

read하는 시간을 기준으로 그전에 commit된 데이터를 읽기 때문에 50 이라는 값을 획득한다.

 

4-2. 격리 레벨 : repeatable read

트랜잭션 시작 시간 기준으로  그전에 커밋된 데이터를 읽는다.

 

4-3. 격리 레벨 : read uncommitted 

MVCC는 커밋된 데이터를 읽기 때문에 이 레벨에서는 보통 mvcc가 적용되지 않는다.

 

mvcc 특징

- 데이터 읽을때 특정 시점 기준 (격리레벨에 따라 상이) 으로 가장 최근 커밋된 데이터를 읽는다.

- 데이터 변화 이력(write)을 관리한다. (위에 작업을 위해서)

- read와 write는 서로를 블로킹하지 않는다.

 

 

<Read Commited 레벨에서의 상황 가정>

초기 x = 50, y = 10

 

T1 -> x가 y에 40을 이체한다. 

T2 -> x에 30을 입금한다.

정상결과값 : x = 40 , y = 50

출처 : 유튜브 '쉬운코드' 님 강의 영상

 

x = 80 , y = 50 이라는 이상한 결과가 나오게 된다. ==> LOST UPDATE 문제가 발생하였다.

 

<격리레벨을 Repeatable Read로 수정하였을 경우>

 

출처 : 유튜브 '쉬운코드' 님 강의내용

위와 같이 먼저 write 락을 취득한 tx이 커밋을 하게 되면 후발주자는 롤백이 되게 된다.

그래서 결과는 x=10, y= 50 이라는 결과가 되어서 정상적으로 돌아간다. (tx2가 재시도 하면 x=40, y=50 이 될 수있으므로 정상)

 

각각의 트랜잭션 마다 격리레벨을 달리 할 수 있지만 T1이 read commited 로 동작하고

T2가 Repeatable Read로 동작하게 된다면 LOST UPDATE는 여전히 발생하게 된다.

 

그렇기 때문에 lost update 에 대한 문제를 해결하기 위해서는

한 작업에서 연관된 트랜잭션에 대해서는 같은 격리레벨을 보장해주어야 한다.

하지만 first-updater-win 은 rdb 마다 다르게 동작한다. mysql에서는 Repeatable Read로 맞춰줘도 이상한 값이 나올 수 있다.

 

mysql은 LOST UPDATE를 예방하기 위해서 Locking read 라는 기법을 사용한다.

(select * from account where id = 'x' for update;) 처럼 write 락까지 같이 획득하게 하는 기법이다.

 

 

read(x)를 하면서 Locking read 이 적용되는 상황이다.

t1이 read(x) 시에 초기값인 50을 읽을 것 같았지만 locking read 에서는 격리 레벨에 상관없이

가장 최근의 커밋된 데이터를 읽게 된다.

 

Locking Read 에는 2가지가 존재한다.

* select ... for update; --> exclusive lock 획득

* select ... for share; --> shared lock 획득

 

<Repeatalbe Read 레벨에서 예상치 못한 결과가 나오는 상황>

x = 10 y = 10 이었을때

T1는 x와 y를 더해 x에 쓰기

T2는 x+y를 y에 쓰기 작업을 한다고 가정하고 

정상결과는 (x , y = 20, 30) or (x , y = 30, 20)

 

 

WRITE SKEW 문제가 발생하게 된다.

 

 

WRITE SKEW 

직접적인 충돌(같은 행 같은 컬럼 갱신)은 없는데, 두 트랜잭션이 동시에 쓰기를 하면서 비즈니스 규칙 혹은 정합성이

깨지는 현상

 

두 트랜잭션이 동시에 실행된다고 가정

  • 비즈니스 규칙:
    데이터베이스에 적어도 한 명의 의사가 항상 당직(on call)이어야 한다.
    → 즉, on_call = true 인 행이 최소 1개는 있어야 함.
  • 트랜잭션 1 (T1):
    Doctor A의 on_call을 false로 바꾼다.
  • 트랜잭션 2 (T2):
    동시에 Doctor B의 on_call을 false로 바꾼다.

각 트랜잭션은 시작 시 스냅샷을 읽었기 때문에 "아직 다른 의사가 on_call 상태다" 라고 생각하고 업데이트를 허용한다.
→ 하지만 둘 다 커밋되면 결과적으로 모든 의사의 on_call = false 가 되어 비즈니스 규칙이 깨진다.

 

 

MYSQL은 Locking Read를 통해 read시에 write락까지 같이 획득하여서 예방할 수 있다.

(정해진 순서로 트랜잭션이 처리되게끔 조절하는 것이라 보면됨)

 

POSTSQL도 Locking Read가 동작하긴 하지만

" 같은 데이터에 먼저 update한 트랜잭션이 커밋하게 되면 나중 트랜잭션은 롤백된다 "

라는 규칙때문에 다른 결과가 발생하게 된다.

 

아니면 격리레벨을 최상위 레벨인 Serializable 로 올려서 어떠한 이상현상 발생을 막을 수 있게 할 수 있다.

하지만 해당 레벨로 설정하면 성능문제가 조금 있을 수 있다. (일단 속도가 느리다. )

 

혹은 커밋 전에 규칙을 재검증하고 위반시 롤백 처리를 해주는 로직을 짤 수도 있다.

 

출처 : https://www.youtube.com/watch?v=-kJ3fxqFmqA