강의/database

[database] DB Lock 2편 (multi-version CC, MVCC)

하기싫지만어떡해해야지 2025. 6. 6. 17:56

본 게시글은

서울대학교 데이터사이언스대학원 이상원 교수님의

데이터사이언스 응용을 위한 빅데이터 및 지식관리시스템 수업을

학습을 목적으로 재구성하였습니다


오늘은 DB lock의 두번째 시간인데

multi-version concurrency control에 대해서 주로 배운다

수업 내용이 저번 시간이랑 겹치는 내용이 많아서

그 부분들은 생략하고 MVCC에 대한 내용만 담아서

내용이 조금 짧다

 

 

우리가 이전 시간까지 수업에서 설명한 내용은

single version의 concurrency control이다

 

lock의 종류는 크게

read lock인 shared lock과

write lock인 exclusive lock이 있고

 

이 lock compatibility table을 통해서

각각 어떻게 호환되는지를 알아보자

 

우선 R(S)와 R(S)간에는 당연히 호환이 가능하다

읽기 lock이 걸려있을 때 다른 트랜잭션이 읽는 것은 상관이없다

 

그러나 R(S)가 걸려있을 때 데이터를 수정하려는 W(X)는 호환이 안되며

데이터를 수정하려고 W(X)가 걸려있을 때는 당연히

R(S)도 W(X)도 허용되지 않는다

 

또한 lock based concurrency control의 성능을 높이기 위해서

isolation level을 4단계로 나누어 관리한다

 

격리수준이 가장 높은 serializable로 가면

동시성은 낮아지지만 데이터 일관성이 높아지고

격리수준이 가장 낮은 read uncommitted로 가면

동시성은 높아지지만 데이터 일관성이 낮아질 수 있다

 

각 isolation level별로 발생할 수 있는

이상현상(anomaly)들을 위 ppt의 표에 정리해놨다

 

아무튼 single version의 입장에서는 이런식으로

4단계의 레벨을 도입해서 lock을 지원을 한다

 

 

 

그렇다면 oracle은 어떻게 concurrency control을 지원하고 있을까

 

원래 CC는 IBM에서 single version으로 지원하고있었는데

oracle이 IBM에 대적하기 위해(?)

multi-version concurrency control을 만들었다고한다

 

이 MVCC는 timestamp 기반으로 각각의 레코드들이

multi version을 갖게 된다

따라서 오라클에서는 

우선 1) lock을 쓰면

2) 데이터는 다양한 버전을 갖고있고

3) 이를 timestamp 기반으로 어느 시점에 commit한 것을

읽을 것인지를 결정한다

이때 버전마다 갖고 있는 timestamp가 

commitSCN(System Change Number)이다

 

대부분의 DBMS는 다 multi-version으로

lock을 관리한다

 

 

일반 single version이랑 multi version은

commit을 하고 그런 과정들은 다 동일하지만

다른 점은 single version은 isolation level이 4개가 있지만

multi version은 isolation level이 read committed, serializable 2개밖에 없다

 

왜냐하면 나머지는 필요가 없기 때문이다

 

 

 

이번엔 Oracle에서 dead lock을 어떻게 관리하는지 보자

 

blocking 당한 상태로 끝없이 기다리고 있는 상태를

dead lock이라고 한다

그렇다면 oracle은 현재 dead lock이 걸려있는지 어떻게 알 수 있을까?

 

DB는 dead lock이 발생했는지 아닌지를 주기적으로 체크한다

가는 길에 cycle을 체크해서 만약 Dead lock 상황이라면

둘 중에 한 놈을 kill 시킨다

 

그런데 생각해보면 사실 dead lock을 미리

prevention 할 수도 있다

만약 내가 지금 이 lock을 실행하면 dead lock이 걸린다는 것을 감지하고

실행하지 않도록 할 수 있다

하지만 이러한 방식대로 구현하려면 굉장히 복잡해지고

dead lock일 확률은 사실 되게 낮은데

그걸 검사하기 위해서 추가적인 연산을 필요로 하게 된다

 

그래서 일반적으로는 dead lock이 발생하면 detect를 하고

둘 중 한개를 kill 시키는 방식을 사용하고

oracle도 이런 방식을 채택하고있다

 

 

 

oracle은 read lock을 걸지않는다

그리고 같은 페이지의 여러 버전을

buffer pool에 복제해놓는다

그런 다음 변경되기 전 버전을 읽어서

dirty read를 방지한다

또한 트랜잭션이 시작한 시점의 고정된 버전만 계속 읽어서

repeatable read도 보장한다

 

오른쪽 예시의 경우 A에서 50을 빼고 B에 50을 더하는

트랜잭션이 있다고 가정하자

그런데 A에서 50을 뺐을 때 sum이라는 select를 했다면

기존의 DB라면 dirty read가 발생하게된다

하지만 oracle은 A에 트랜잭션이 수행되기전과 후 버전이 있기 때문에

sum read를 할 때 A에 트랜잭션이 수행되기 전 버전을 가져와서

데이터의 일관성을 유지할 수 있는 것이다

 

그렇다면 phantom read는 어떤 방식으로 방지할까?

각 트랜잭션과 레코드는 commit 할 때 SCN이 부여된다

따라서 SCN을 기준으로 필터링이 되어서

select시 해당 시점 이전에 커밋된 레코드만 조회해서

새로운 레코드가 나오는 것을 방지한다

 

 

 

지금까지 oracle은 트랜잭션이 시작될 때의 시점(SCN)을 기준으로 

데이터를 보장한다는 것을 배웠다

만약 데이터가 이후 트랜잭션에 의해서 수정되었더라도

undo segment에 저장된 이전 데이터를 보여준다

 

정리하자면 oracle은 Read lock을 걸지 않는다

reader가 writer를 blocking하지 않고

데이터 일관성을 위해서 수정된 레코드의 예전 버전들을

undo segment에 다 저장해놓는다

그 다음 트랜잭션 시작 시점인 SCN 이후의 레코드들은 무시하고

이전 상태의 데이터만 보여주게 되는데

이로써 일관된 snapshot을 제공할 수 있다

 

그렇다면 이전 버전의 데이터들을

언제까지 저장할 수 있을까?

undo segment의 저장용량이 꽉 차서

더이상 데이터를 저장할 수 없을 경우

DBMS는 그냥 과거의 데이터를 지워버린다

이 상황에서 트랜잭션이 지워버린 버전의 데이터를 요구하면

그때 DBMS는 snapshot too old라고 하는

일종의 배째라(?) 에러를 내버린다고 한다

 

뭐 지금은 디스크 가격이 싸서 저런 경우가 많이 없다고 하는데

예전에 리소스를 제한할 때에는

저런 에러메시지를 많이 띄웠다고 한다

 

 

 

oracle에서의 isolation level과

ANSI 표준의 isolation level을 비교해보자

 

앞에서 말햇지만 일반적인 isolation level은

4개의 단계로 나뉜다

 

하지만 oracle같은 경우는 MVCC를 지원하기 때문에

2개의 level은 필요가 없어져서

serializable과 read committed만 남게 된다