유튜브 채널 쉬운코드의 강의 영상으로 공부하면서 작성한 게시글입니다.

키워드 : MVCC, read committed, repeatable read, read skew, serializable, FOR UPDATE


MVCC (Multi Version Concurrency Control)

데이터를 덮어쓰지 않고, 여러 버전을 만들어서 동시에 읽기/쓰기를 가능하게 하는 방식

 

Lock 공부를 할 때 lock은 같은 데이터에 대해 read-read 경우만 block이 안 걸리기 때문에 동시 처리량이 좋지 않다고 했었다.

그래서 MVCC가 개발됐는데, 이걸 사용해서 동시성 제어를 하면 같은 데이터에 대해 write-write 경우에 대해서만 한쪽이 block되고 그 외의 경우에는 동시에 처리가 가능하다. 덕분에 동시 처리량도 더 좋아진다.

 

예제

x = 10 상태에서 두개의 트랜잭션을 실행한다.

  1. x를 읽는다
  2. x를 50으로 바꾼다

write 앞에 write_lock은 생략. unlock도 commit뒤에 있는데 생략되어있다.

더보기

recoverability(회복 가능성)를 위해.

자세한 내용은 recoverability 글을 보자.

rollback되는 경우를 대비해서 롤백이 잘 되도록 하기위해서 락을 쥐고있는 것.

취소될지도 모르는 데이터를 남이 읽지 못하도록 격리하는 것.

K = 100, H = 200인 상태에서

K가 H에게 20만원을 이체하고, H도 본인 계좌에 30만원을 입금한다 하자.

이미지와 같은 순서로 진행되면 T2가 실패하면서 rollback을 해야하고, 이에 영향을 받은 T1도 롤백을 해줘야 하는데, T1은 이미 commit이 된 상태이므로 롤백이 불가능하다(durability 속성 때문).

이를 해결하기 위해서는 T2가 먼저 commit/rollback 을 하고, T2에 의존하는 T1이 commit/rollback을 해야한다.

그런데 이러면 T2가 rollback하면 반드시 T1로 롤백해야되고, 이런 연쇄(cascading rollback)가 길어지면 성능에 큰 악영향을 미친다.  이것을 방지하기 위해 commit 되지 않은 tx가 write한 데이터는 읽지 않도록 하면 T2가 실패한 일은 그냥 없었던 일이고, T1은 T2이 write한걸 사용하지 않기 때문에 그냥 작업을 이어가면 굳이rollback 하지 않아도 된다. (cascadeless schedule)

MVCC에서는 일반 SELECT는 row-level lock을 사용하지 않으며 read가 write를 블로킹하지 않는다.

tx2가 write를 db에 하지 않고, 먼저 자신(트랜잭션)만 아는 공간에 기록한다.

그 후에 tx1이 read를 하지만 tx2의 변경이 DB에 commit되지 않았기 때문에 DB에 는 아직 10이 적혀있고 그걸 읽는다.

 MVCC의 동작

  • 데이터를 읽을 때 특정 시점 기준으로 가장 최근에 commit한 데이터를 읽는다
  • 기준이 되는 시점은 isolation level에 따라 다르다.
  • 데이터 변화(write) 이력을 관리한다
  • read와 write는 서로block하지 않는다

만약 T1이 T2커밋 이후에 x를 한번 더 읽으면 어떻게될까? 그건 T1의 isolation level에 따라 다르다.

read uncommitted level은 존재는 하는데 read committed level 처럼 동작한다

read committed 라면 read 시간 기준으로 그전에 commit된 데이터를 읽는다. 그래서 50을 읽음. mysql postgresql 모두 동일.

repeatable read 라면 tx 시작 시간 기준으로 그전에 commit된 데이터를 읽는다. 즉 10을 읽음. mysql postgresql 모두 동일.

serializable 이라면 SSI(Serializable Snapshot Isolaion) 기법이 적용된 MVCC로 동작한다. postgresql 만 해당

SSI는 정확히 뭔지 강의자도 잘 모른다고 한다(?)

 

예제

x = 50, y = 10 상태. 2개의 트랜잭션을 실행한다.

  1. x가  y에 40을 이체한다
  2. x에 30을 입금한다

read committed level

read committed level에서도 이상현상이 발생한다. (Lost Update)

x = 40 y = 50이 되어야 하는데 위와같은 순서로 진행된다면 x = 80 y = 50이 되어버린 것.

이건 isolation level을 높이면 해결할 수 있다.

repeatable read

tx1은 read committed, 2는 repeatable read로 바뀌었다.

read committed와 똑같이 동작하지만, write를 할 때 같은 데이터에 먼저 업데이트한 tx가 commit되면 나중 tx 는 rollback 된다. 하나는 commit, 하나는 rollback이 되었기 때문에 이 결과는 제대로된 결과가 되는 것.

 

그럼 이 예제는 tx2만 repeatable read면 되는걸까?

순서만 바뀌어도 Lost Update 가 발생한다.

그러니 한 tx의 isolation level만 챙겨주면 되는게 아니라 연관된 다른 tx도 챙겨줘야한다.

 

repeatable read에서의 read skew 문제

두 트랜잭션의 isolation level이 repeatable read일때도 발생하는 이상현상을 알아보자.

예제

x = 10, y = 10 상태. 두 트랜잭션을 실행한다.

  1. x와 y를 더해서 x에 쓴다
  2. x와 y를 더해서 y에 쓴다

정상적인 결과라면 한쪽은 20 한쪽은 30이 되어야 하는데, 20 20이 되었다. 이런 현상을 write skew라고 한다.

이 현상은 mysql, postgresql 둘 다에서 발생할 수 있다. 이 현상을 방지하는 방법은 read SQL을 쓸 때 끝에 FOR UPDATE를 적어서 read 작업에 write_lock을 걸어주는 것이다.

SELECT balance FROM account WHERE id = 'x' FOR UPDATE; --postgreSQL write lock 획득
SELECT balance FROM account WHERE id = 'x' FOR SHARE; --postgreSQL read lock 획득

postgreSQL에서는 FOR UPDATE로 인해 기다렸던 read(x)를 lock을 받고 실행할 때 실패하고, rollback 해버린다.

serializable

mysql에서는 tx의 모든 select문은 암묵적으로 select ...for share처럼 동작한다.

postgresql은 SSI(Serializable Snapshot Isolaion)로 구현되어있다.


MVCC의 구현 방법은 RDBMS마다 다르기 때문에 특정 조건에서 다르게 동작할 수 있다.

내가 쓰는 RDBMS 의 특징을 잘 알아두는게 좋다.

'공부 > DB' 카테고리의 다른 글

DB Partitioning, Sharding, Replication  (0) 2026.02.19
postgreSQL EXPLAIN ANALYZE  (0) 2026.02.18
DB LOCK  (0) 2026.02.18
postgreSQL isolation level  (0) 2026.02.18
postgreSQL Transaction 트랜잭션, ACID  (0) 2026.02.16

+ Recent posts