Database

MVCC와 잠금 없는 일관된 읽기

MVCC(Multi Version Concurrency Control)란?

MVCC는 레코드 레벨의 트랜잭션을 지원하는 DBMS가 제공하는 기능이며, 락을 사용하지 않는 일관된 읽기를 제공하는 것을 목적으로 한다. MVCC를 InnoDB는 언두 로그(Undo Log)를 이용해 구현한다.
MVCC에서 MV, 즉 Multi Version은 하나의 레코드에 대해 여러 버전이 동시에 관리된다는 것을 나타낸다.

예시를 통해 MVCC에 대해 자세히 알아보자.
격리 수준이 READ_COMMITTED인 InnoDB 스토리지 엔진을 사용하는 MySQL 서버에서 테이블의 데이터 변경을 진행한다.

INSERT INTO member (m_id, m_name, m_area) VALUES (12, '서민정', '부산');

먼저, 한명의 유저를 member 테이블에 삽입한다.
그 때의 InnoDB 버퍼 풀과 언두 로그의 상태는 다음과 같다.

  • InnoDB 버퍼 풀: (12, 서민정, 부산)
  • 언두 로그: 비어있음.


이제, id가 12번인 서민정의 area 필드를 부산에서 경기로 변경해보자.

UPDATE member SET m_area = '경기' WHERE m_id = 12;

이 때, member에 대해 업데이트를 수행하면 m_area 컬럼의 변경 전 값만 언두 로그로 복사된다.
UPDATE 문장이 실행되면 커밋 실행 여부와 관계없이 InnoDB의 버퍼 풀은 새로운 값인 '경기'로 업데이트된다.

  • InnoDB 버퍼 풀: (12, 서민정, 경기)
  • 언두 로그: (12, 부산)


아직 COMMIT이나 ROLLBACK이 되지 않은 상태에서 다른 사용자가 조회 쿼리를 날리게 되면 어디의 데이터를 조회해올까?

이 질문은 설정된 격리 수준에 따라 달라지는데, READ_UNCOMMITED의 경우에는 InnoDB 버퍼 풀 및 데이터 파일로부터 데이터를 읽어서 반환한다. 즉, 커밋되든, 커밋되지 않았든 변경된 상태의 데이터를 반환한다.
그렇지 않고 READ_COMMITTED나 그 이상의 격리 수준의 경우에는 아직 커밋되지 않았기 때문에 언두 로그의 데이터를 읽어서 반환하다. 이 과정을 DBMS에서의 MVCC라고 한다.

즉, MVCC란 하나의 레코드에 대해 2개의 버전이 유지되고, 필요에 따라 보여지는 데이터가 달라지는 구조를 말한다.

 

 

잠금 없는 일관된 읽기(Non-Locking Consistent Read)란?

InnoDB 스토리지 엔진은 MVCC 기술을 이용해 락을 걸지 않고 읽기 작업을 수행한다. 락을 걸지 않기 때문에 InnoDB에서 읽기 작업은 다른 트랜잭션이 가지고 있는 락을 기다리지 않고, 읽기 작업을 수행할 수 있다. (격리 수준이 SERIALIZABLE의 경우에는 순수한 읽기 작업의 경우에도 락을 필요로 한다.)

즉, 특정 사용자가 레코드를 변경하고 아직 커밋을 수행하지 않았다고 하더라도, 이 변경이 다른 트랜잭션의 사용자가 진행하는 SELECT 작업을 방해하지 않는다. 이를 잠금 없는 일관된 읽기라고 하며, 변경되기 전의 데이터를 읽기 위해 언두 로그를 활용한다.

예시를 통해 알아보자.
만약, 테이블에 다음과 같은 데이터가 저장되어있다고 가정해보자.

INSERT INTO member (m_id, m_name, m_area) VALUES (1, '서민정', '부산'); 
INSERT INTO member (m_id, m_name, m_area) VALUES (2, '조앤', '경기'); 
INSERT INTO member (m_id, m_name, m_area) VALUES (3, '우형', '서울');


그리고, A라는 사용자가 id 2번 데이터의 m_area를 잠실로 변경하는 트랜잭션 작업을 수행하고 있는 중이다.
이 때, B라는 사용자가 id 2번 데이터에 대해 SELECT 요청을 하게 되면, B 사용자는 A 사용자의 UPDATE 요청에 방해받지 않고 변경되기 전의 데이터인 (2, '조앤', '경기')라는 결과를 조회할 수 있다.

즉, 잠금 없는 일관된 읽기란 InnoDB에서 읽기 작업에 대해 Lock을 걸지 않기에 다른 트랜잭션이 가진 Lock을 기다리지 않고, 읽기 작업을 수행할 수 있는 것을 말한다.