[DB] MVCC
기존 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