DB MVCC

지식저장공간·2023년 6월 1일
0

DB

목록 보기
12/19

MVCC

MultiVersion Concurrency control


기존 MVCC 도입 전 Lock만을 이용했을 때는 Read - Read lock끼리만 같은 데이터 접근이 가능했다.

데이터 베이스 lock : read - read일 경우에만 지연없이 처리가 가능하다.
한 트랜잭션이 write lock 획득 시 다른 트랜잭션은 read/write 둘다 불가능하다.
read lock을 획득하면 다른 트랜잭션이 write lock일 경우에도 처리 불가능.
성능이 저하되어 새로운 방법을 고안하게된다.

서로 같은 데이터에 대해서 서로 다른 트랜잭션이 write - write인 경우에만 처리가 불가능하고 write lock 취득 후 다른 트랜잭션은 read가 가능하며, 다른 트랜잭션이 read lock 획득하여도 다른 트랜잭션은 read / write 둘다 가능하다.

예제 및 특징

예제1

MVCC 는 commit 된 데이터만 읽는다. 최소 isolation level이 read committed이다. 트랜잭션2는 아직 commit을 하지 않았기 때문에 트랜잭션1은 x의 값을 10으로 읽는다.

한 트랜잭션이 commit 후 unlock을 시도한다. recoverability를 위해 commit할 때 write lock을 unlock 한다.

트랜잭션 1의 isolation level이 read committed인 경우 커밋된 값만 읽기 때문에 트랜잭션2가 commit한 후 다시 x에 대한 값을 읽는다면, x는 50이다. (MySQL, PostgreSQL)

isolation level이 repeatable read인 경우 트랜잭션의 시작 시간을 기준으로 트랜잭션이 그 전에 commit된 데이터를 읽기 때문에 트랜잭션2가 x의 값을 바꾸기 전인 10을 읽는다. (MySQL, PostgreSQL)

MVCC의 특징

MVCC 는 데이터를 읽을 때 특정 시점 기준으로 가장 최근에 commit된 데이터를 읽는다.

이전 예제 read-committed : 50, repeatable-read : 10

특정 시점이란, isolation level에 따라 다르다.

데이터 변화 이력을 관리하며, read와 write는 서로를 block하지 않는다.

Consistent read : 특정 시점을 기준으로 데이터를 read한다.

isolation level이 read uncommtitted인 경우 보통 MVCC가 적용되지 않는다.

예제2

결과 기댓값 : x = 40, y = 50

MVCC를 사용하면 값을 직접적으로 읽고, 쓰는게아닌 스냅샷 형태로 트랜잭션 내부에서 관리한다.

트랜잭션2는 트랜잭션1이 x에 대해 write lock을 가지고 있어도 read는 가능하기 때문에 x의 값을 읽는다. 하지만, write 작업은 불가능하여 값을 추가하지 못한다.

트랜잭션1은 x와 y에 대해 write lock을 획득하여 성공적으로 작업 후 커밋한다.

트랜잭션 1이 x에 대해 commit하고 write lock을 반납 후 트랜잭션 2는 write를 시작한다.

결과값이 x= 80, y = 50. Lost Update가 발생한다. 업데이트 된 정보가 사라진다.

트랜잭션2의 isolation level을 repeatable read로 변경한 경우

PostgreSQL인 경우에는 같은 데이터에 대해서 트랜잭션들이 write할 경우
먼저 update를 실행한 트랜잭션이 commit하면 나중에 update한 트랜잭션은 롤백된다. (first-updater-win)

트랜잭션마다 서로 다른 isolation level을 설정할 수 있다.

예제 3

트랜잭션1번과 트랜잭션2번의 isolation level이 둘다 read-committed이고, 트랜잭션2가 먼저 시작하는 경우

트랜잭션2가 먼저 시작하고 트랜잭션1이 나중에 시작할 경우. 트랜잭션1은 write를 수행하지 못하고 lock을 획득할 때 까지 기다렸다가 트랜잭션 2가 lock 반환시점에 write를 하게된다. 트랜잭션2 결과 x = 80 이지만, 트랜잭션1의 결과 x=10이다. (Lost Update 발생)

트랜잭션1번의 isolation level이 repeatable-read 이고, 트랜잭션2가 먼저 시작하는 경우

트랜잭션 1이 repeatable read인 경우 나중에 update를 진행하기 때문에 rollback이 된다.

즉, 데이터의 정합성이 충족시키려면 하나의 트랜잭션의 isolation level만 신경쓰는것이 아니라, 모든 트랜잭션의 level을 관리해주어야한다.

MySQL의 경우 나중에 update하는 트랜잭션이 rollback이 일어나지 않아 그대로 commit해버리고, Lost Update가 발생한다.

MySQL의 MVCC

Locking read 예제1

MySQL에서는 repeatable read만으로 Lost Update를 해결할 수가 없다. 따라서 Locking read를 사용한다.

select ... for update; 개발자가 직접 for update 문을 작성해야한다. select for update를 사용하면 read할 때에도 write lock을 획득하게 된다.

트랜잭션2는 x를 read할 때 write lock을 획득하고, 트랜잭션1도 그냥 read하는것이 아닌, locking read로 실행하고 write lock을 획득해야한다. 때문에 트랜잭션1은 x의 값을 바로 read하지 못하고 트랜잭션 2가 lock을 반납할 때 까지 기다린다.

트랜잭션1의 시작 시점인 x의 값 50을 읽는것이 아니라, MySQL에서 locking read는 가장 최근 commit된 데이터를 읽는다. (x=80)

locking read는 read할 때 write lock을 획득한다. 그 후 데이터를 읽을때에는 가장 최근 commit시점에서의 데이터를 읽는다. MYSQL에서는 repeatable-read 레벨에서 Lost Update를 방지하기 위해 Locking read를 사용해야한다.

SELECT... FOR UPDATE : Write Lock
SELECT... FOR SHARE : Read Lock

예제2

각 트랜잭션은 x와 y의 값을 읽고 서로에게 값을 더해준다.

최종 결과 예상 읽는 순서에 따라 값이 달라진다. 20/30, 30/20

locking read없이 동작시킨 경우

WRITE SKEW 발생 : 트랜잭션을 시작할 때의 데이터 내용과 트랜잭션이 끝나기 전의 데이터가 다르다. 20/30 또는 30/20이 출력되어야 하지만, 20/20인 상태

locking read를 설정하여 동작

트랜잭션1은 x에 대해 read할 때 write lock을 획득하고, locking read 때문에 트랜잭션2는 x에 대한 데이터를 read하기 위해서는 트랜잭션1이 write lock을 반환할때 까지 대기해야한다.

트랜잭션1이 lock 반환 후 트랜잭션2는 x에 대해 read를 진행

값이 정확하게 나온다. Write skew 해결 즉, Write skew를 해결하기 위해서 locking read를 사용해야한다.

예제3(PostgreSQL)

PostgreSQL의 경우 locking read를 사용하면 나중에 update하는 트랜잭션은 rollback이 된다. MySQL과 동작방식이 다르기 때문에 결과가 다르다.

예제4(PostgreSQL)

트랜잭션2는 x,y에 대해 for share를 사용해서 read lock만을 획득하려고 해도, 트랜잭션1이 x와 y에대해 먼저 commit하기 때문에 트랜잭션2는 roll back이 발생한다.

Serializable

MYSQL

lock을 사용하는 방식처럼 동작한다.

PostgreSQL

정리

출처 : 쉬운코드 유튜브

profile
발전하는 개발자가 꿈입니다. 지식을 쌓고 지식을 활용해 목표 달성을 추구합니다.

0개의 댓글