Database

[DATABASE] MVCC (MultiVersion Concurrency Control)

ju_young 2023. 12. 10. 15:24
728x90

같은 데이터에 대해서 서로 다른 트랜잭션이 동시에 write를 하는 것만 허용을 하지 않고 그 외에 read/write 또는 read/read하는 경우를 허용한다. 따라서 Lock을 사용했을 때보다 처리량이 많아진다.

MVCC?

데이터를 읽을 때 특정 시점(Isolation Level)을 기준으로 가장 최근에 commit된 데이터를 읽는다. 또한 데이터가 변할 때 이력을 따로 저장하여 관리한다.

 

x=10일 때 다음 작업을 수행한다고 해보자. (lock, unlock은 생략)

 

transaction1 transaction2
  write(x=50)
read(x) => 10  
  commit
read(x) => 50  
  • transaction2에서 write(x=50)을 수행하면 별도의 공간에 변경된 값을 저장한다.
  • MVCC는 commit된 데이터만 읽기 때문에 transaction2에서 write하기 전의 값을 read한다.
  • transaction2에서 commit을 수행하면 transaction1은 isolation level이 read committed일 때 commit된 값인 50을 read한다. 하지만 isolation level이 repeatable read라면 트랜잭션 시간 기준으로 commit된 값인 10을 read한다.

NOTE
read uncommitted level에서는 보통 MVCC가 적용되지 않는다. PostgreSQL의 경우 read uncommitted가 존지해지만 read committed처럼 동작한다.

PostgreSQL

Lost Update

x=50, y=10이고 두 트랜잭션 모두 read committed level을 가질때 다음과 같은 동작을 수행할 경우 transaction1에서 x를 10으로 변경한 부분이 lost된다.

 

transaction1(read committed) transaction2(read committed)
read(x) => 50  
write(x=10)  
  read(x) => 50
  write(x=80)
raad(y) => 10  
write(y=50)  
commit  
  write(x=80)
  commit

위와 같은 lost update 문제를 해결하기위해 transaction2의 level을 repeatable read로 변경할 수 있다. 그러면 아래처럼 transaction2가 rollback된다.

 

repeatable read는 같은 데이터에 먼저 update한 transaction이 commit을 하면 나중에 update를 시도한 transaction은 rollback이 되기 때문이다.

 

transaction1(read committed) transaction2(repeatable read)
read(x) => 50  
write(x=10)  
  read(x) => 50
  write(x=80)
raad(y) => 10  
write(y=50)  
commit  
  rollback

transaction2가 먼저 시작할 때도 마찬가지로 lost update 문제가 발생하기 때문에 transaction1의 level도 repeatable read로 변경하면 해결할 수 있다. 따라서 Lost Update 문제를 해결하기위해 트랜잭션에 Repeatable Read를 적용할 수 있다.

Write Skew

PostgreSQL은 MySQL에서처럼 SELECT ~ FROM ~ WHERE ~ FOR UPDATELocking Read를 적용할 수 있다. 그리고 이 Loking Read를 적용하면 Write Skew 문제를 해결 할 수 있다.

 

transaction1(repeatable read) transaction2(repeatable read)
read(x) => 10 (Locking Read)  
  read(x) (Lock 대기)
read(y) => 10 (Locking Read)  
write(x=20)  
commit (Lock 반환)  
  rollback

MySQL

Lost Update

MySQL은 PostgreSQL과 달리 read를 할 때 Locking Read를 적용할 수 있다. 쿼리로는 SELECT ~ FROM ~ WHERE ~ FOR UPDATE로 작성할 수 있다. 이 때 FOR UPDATE를 직접 추가해주어야한다.

 

Locking Read는 read 뿐만 아니라 write의 lock도 획득할 수 있다. 또한 가장 최근에 commit된 데이터를 읽기 때문에 아래에서 transaction1이 commit하고 난 후 transaction2가 read(x)할 때 50이 아닌 80을 읽게되는 것이다.

 

transaction1(repeatable read) transaction2(repeatable read)
read(x) => 50 (Locking Read)  
  read(x) => 50 (Lock 대기)
write(x=80)  
commit (Lock 반환)  
  read(x) => 80 (Locking Read)
  write(x=40)
  read(y) => 10 (Locking Read)
  write(y=50)
  commit (Lock 반환)

Locking Read

Locking Read는 다음 두 가지로 나누어진다.

  • SELECT ~ FROM ~ WHERE ~ FOR UPDATE : write lock
  • SELECT ~ FROM ~ WHERE ~ FOR SHARE : read lock

Write Skew

Locking Read를 사용하면 Write Skew 문제도 해결할 수 있다.

 

transaction1(repeatable read) transaction2(repeatable read)
read(x) => 10 (Locking Read)  
  read(x) (Lock 대기)
read(y) => 10 (Locking Read)  
write(x=20)  
commit (Lock 반환)  
  read(x) => 20 (Locking Read)
  read(y) => 10 (Locking Read)
  write(y=30)
  commit (Lock 반환)
728x90