인생을 코딩하다.

[Real MySQL 정리], 3장 MySQL 아키텍처 본문

DataBase

[Real MySQL 정리], 3장 MySQL 아키텍처

Hyung1 2021. 4. 25. 22:07
728x90
반응형

MySQL 아키텍처

MySQL의 전체 구조

MySQL 서버는 크게 MySQL 엔진스토리지 엔진으로 구분해서 볼 수 있다.

1

MySQL 엔진

클라이언트로부터 접속 및 쿼리 요청을 처리하는 커넥션 핸들러SQL파서 및 전처리기, 쿼리의 최적화된 실행을
위한 옵티마이저가 중심을 이룬다. 요청된 SQL 문장을 분석하거나 최적화하는 등 DBMS 두뇌에 해당하는 처리를 수행한다.

스토리지 엔진

실제 데이터를 디스크 스토리지에 저장하거나 디스크 스토리지로부터 데이터를 읽어오는 부분은 스토리지 엔진
전담한다. MYSQL 서버에서 MySQL 엔진은 하나지만 스토리지 엔진은 여러개를 동시에 사용할 수 있다.
다음 예제와 같이 테이블이 사용할 스토리지 엔진을 지정하면 이후 테이블의 모든 읽기 작업이나 변경 작업은 정의된
스토리지 엔진이 처리한다.

mysql > CREATE TABLE test_table (fd1 IT, fd2 INT) ENGINE = INNODB;

핸들러 API

MySQL 엔진의 쿼리 실행기에서 데이터를 쓰거나 읽어야 할 때는 각 스토리지 엔진에게 쓰기 또는 읽기를 요청하는데,
이러한 요청을 핸들러(Handler) 요청이라고 하고, 여기서 사용되는 API를 핸들러 API라고 한다.

"SHOW GLOBAl STATUS LIKE 'Handler%';"

명령으로 이 핸들러 API를 통해 얼마나 많은 데이터(레코드) 작업이 있엇는지 확인가능하다.


MySQL 스레딩 구조

2

MySQL은 프로세스 기반이 아니라 스레드 기반으로 작동하며, 크게 포그라운드 스레드백그라운드 스레드로 구분할 수 있다.

포그라운드 스레드(클라이언트 스레드)

포그라운드 스레드는 최소한 MySQL 서버에 접속된 클라이언트의 수만큼 존재하며, 주로 각 클라이언트 사용자가 요청하는 쿼리 문장을 처리하는 것이 임무다. 사용자가 작업을 마치고 커넥션을 종료하면, 해당 커넥션을 담당하던 스레드는 다시 스레드 캐시(Thread pool)로 되돌아간다. 이때 이미 스레드 캐시에 일정 개수 이상의 대기 중인 스레드가 있으면 스레드 캐시에 넣지 않고 스레드를 종료시켜 일정 개수의 스레드 만 스레드 캐시에 존재하게 한다.


이렇게 스레드의 개수를 일정하게 유지하게 만들어주는 파라미터가 thread_cache_size다. InnoDB 테이블은 데이터 버퍼나 캐시까지만 포그라운드 스레드가 처리하고, 나머지 버퍼로부터 디스크까지 기록하는 작업은 백그라운드 스레드가 처리한다.

백그라운드 스레드

인서트 버퍼를 병합하는 스레드 로그를 디스크로 기록하는 스레드, InnoDB 버퍼 풀의 데이터를 디스크에 기록하는 스레드, 데이터를 버퍼로 읽어들이는 스레드, 그리고 기타 여러 가지 잠금이나 데드락을 모니터링 하는 스레드가 있다.
이러한 모든 스레드를 총괄하는 메인 스레드도 있다.

 

가장 중요한 것은 로그 스레드(Log thread)와 버퍼의 데이터를 디스크로 내려쓰는 작업을처리하는 쓰기 쓰레드 일 것이다.

 

읽기 / 쓰기 쓰레드의 개수를 지정하는 파라미터 : innodb_write_io_threads / innodb_read_io_threads

클라이언트가 서버에 접속하게 되면 MySQL 서버는 그 클라이언트의 요청을 처리해 줄 스레드를 생성해 그 클라이언트에게 할당해준다.
이 스레드는 DBMS의 앞단에서 사용자(클라이언트와) 통신하기 때문에 포그라운드 스레드라고 하며, 또한 사용자가 요청한 작업을 처리하기
떄문에 사용자 스레드라고도 한다.

메모리 할당 및 사용구조

3

MySQL에서 사용되는 메모리 공간은 크게 글로벌 메모리 영역로컬 메모리 영역 으로 구분 할 수 있다.
글로벌 메모리 영역의 모든 메모리 공간은 MySQL 서버가 시작되면서 무조건 운영체제로부터 할당된다. 글로벌 메모리 영역과 로컬 메모리 역역의 차이는 MySQL 서버 내에 존재하는 많은 스레드가 공유해서 사용하는 공간인지 아닌지에 따라 구분되며 각각 다음과 같은 특성이 있다.

  • 글로벌 메모리 영역
    • 하나의 메모리 공간만 할당된다. 모든 스레드에 의해 공유된다.
  • 로컬 메모리 영역
    • MySQL 서버상에 존재하는 클라이언트 스레드가 쿼리를 처리하는데 사용하는 메모리 영역. 클라이언트 영역이라고도 함.
    • 클라이언트와 MySQL 서버와의 커넥션을 세션 이라고 하기 때문에 세션 메모리 영역이라고도 한다.
    • 로컬 메모리는 각 클라이언트 스레드별로 독립적으로 할당되며 절대 공유되어 사용되지 않는다는 특징이 있다.
    • 각 쿼리의 용도별로 필요할 때만 공간이 할당되고 필요하지 않은 경우에는 MySQL이 메모리 공간을 할당조차도 하지 않을 수도 있다는 점이다.
      대표적으로 소트 버퍼조인 버퍼등이 그러하다.
    • 커넥션이 열려 있는 동안 계속 할당된 상태로 남아 있는 공간도 있고(커넥션 버퍼나 결과 버퍼) 그렇지 않고 쿼리를 실행하는 순간에만 할당헀다가
      다시 해제하는 공간(소트 버퍼나 조인 버퍼)도 있다.

MySQL 엔진과 스토리 엔진의 영역 처리

SQL 파서 <-> SQL 옵티마이저 <-> SQL 실행기 <-> 데이터 읽기/쓰기 <-> 디스크 스토리지

 

MySQL에서 쿼리가 실행되는 과정을 크게 나눈다면 거의 대부분의 작업이 MySQL 엔진(SQL 파서, 옵티마이저, 실행기)에서 처리되고, 마지막 "데이터 읽기/쓰기" 작업만 스토리지 엔진에 의해 처리된다.(아주 새로운 용도의 스토리지 엔진을 만든다 하더라도 DBMS의 전체 기능이 아닌 일부분의 기능만 수행하는 엔진을 작성하게 된다는 의미) 하지만, 실질적인 GROUP BY나 ORDER BY등 많은 복잡한 처리는 스토리지 엔진 영역이 아니라 MySQL 엔진의 처리 영역인 쿼리 실행기에서 처리된다.


쿼리 실행 구조

4

파서

사용자 요청으로 들어온 쿼리 문장을 토큰(MySQL이 인식할 수 있는 최소 단위의 어휘나 기호)으로 분리해 트리 형태의
구조로 만들어 내는 작업을 의미한다. 쿼리문장의 기본 문법 오류는 이 과정에서 발견되며 사용자에게 오류 메시지를 전달하게 된다.

진 처리기

파서 과정에서 만들어진 파서 트리를 기반으로 쿼리 문장에 구조적인 문제점이 있는지 확인한다. 각 토큰을 테이블 이름이나 칼럼 이름 또는 내장 함수와 같은 개체를 매핑에 해당 객체의 존재 여부와 객체의 접근권한 등을 확인하는 과정을 이 단계에서 수행한다. 존재 하지 않거나 권한상 사용할 수 없는 개체의 토큰은 이 단계에서 걸러진다.

옵티마이저

사용자의 요청으로 들어온 쿼리 문장을 저렴한 비용으로 가장 빠르게 처리할지 결정하는 역할을 한다.

실행 엔진

실행 엔진은 만들어진 계획대로 각 핸들러에게 요청해서 받은 결과를 또 다른 핸들러 요청의 입력으로 연결하는 역할을 수행한다.

 

옵티마이저가 GROUP BY를 처리하기 위해 임시 테이블을 사용하기로 결정했다고 가정했을때,

ex) 실행엔진이 핸들러에게 테이블을 만들어, WHERE 절에 일치하는 레코드 읽어와, 읽어온 레코드들 임시 테이블로 저장해등을 요청하고 최종적으로 실행 엔진은 결과를 사용자나 다른 모듈에게 넘긴다.

핸들러(스토리지 엔진)

MySQL 서버의 가장 밑단에서 MySQL 실행 엔진의 요청에 따라 데이터를 디스크로 저장하고 디스크로부터 읽어오는 역할을 담당한다.


복제

5

MySQL의 복제는 레플리케이션이라고도 하는데, 복제는 2대 이상의 MySQL 서버가 동일한 데이터를 담도록 실시간으로 동기화 하는 기술이다.


복제에는 INSERT나 UPDATE와 같은 쿼리를 이용해 데이터를 변경할 수 있는 MySQL서버와 SELECT 쿼리로 데이터를 읽기만 할 수있는 MySQL서버로 나뉜다.


MySQL에서는 쓰기와 읽기 역할로 구분해, 전자를 마스터라고 하고 후자를 슬레이브라고 한다. 서버의 복제에서는 머스터는 반드시 1개이며 슬레이브는 1개 이상으로 구성될 수 있다.

  • 마스터
    • MySQL의 바이너리 로그가 활성화되면 어떤 MySQL 서버든 마스터가 될 수 있다. 애플리케이션의 입장에서 본다면 마스터 장비는
    • 주로 데이터가 생성 및 변경, 삭제되는 주체(시작점)이라고 볼 수 있다. 일반적으로 복제에 참여하는 여러 서버 가운데 변경이 허용되는 서버는 마스터로 한정할 때가 많다.
    • 그렇지 않은 경우 복제되는 데이터의 일관성을 보장하기 어렵기 때문이다. 슬레이브 서버에서 변경 내역을 요청하면 마스터 장비 는 그 바이너리 로그를 읽어 슬레이브로 넘긴다. 마스터 장비의 프로세스 가운데 "Bimlog dump"라는 스레드가 이 일을 전담하는 스레드다.
  • 슬레이브
    • 데이터(바이너리 로그)를 받아 올 마스터 장비의 정보(IP주소와 포트 정보 및 접속 계정)을 가지고 있는 경우 슬레이브가 된다. (마스터나 슬레이브라고 해서 별도의 빌드 옵션이 필요하거나 프로그램을 별도로 설치해야 하는 것은 아니다.)마스터 서버가 바이너리 로그를 가지고 있다면 슬레이브 서버는 릴레이 로그를 가지고 있다.
    • 슬레이브 서버의 I/O 스레드는 마스터 서버에 접속해 변경 내역을 요청하고 받아 온 변경 내역을 릴레이 로그에 기록한다.그리고 슬레이브 서버의 SQL 스레드가 릴레이 로그에 기록된 변경 내역을 재실행함으로써 슬레이브의 데이터를 마스터와 동일한 상태로 유지한다. I/O 스레드와 SQL 스레드는 마스터 MySQL에서는 가동되 않으며, 복제가 설정된 슬레이브 MySQL 서버에서 자동적으로 가동하는 스레드다.

복제를 사용할 경우 주의할 점

  • 슬레이브는 하나의 마스터만 설정 가능
  • 마스터와 슬레이브의 데이터 동기화를 위해 슬레이브는 읽기 전용으로 설정
  • 슬레이브 서버용 장비는 마스터와 동일한 사양이 적합
    • 여러 개의 스레드로 실행된 쿼리가 슬레이브에서 지연되지 않고 하나의 스레드로 처리될 수 있다.
    • 데이터 변경은 데이터 조회보다는 10분의 1 수준으로 유지되는 것이 일반적이므로 마스터 서버와 슬레이브 서버를 같은 사양으로 유지할 때가 많다.
    • 또한, 슬레이브 서버는 마스터 서버가 다운된 경우 그에 대한 복구 대안으로 사용될 떄도 많기 때문에 사양을 동일하게 맞추는 경우가 대부분이다
  • 복제가 불필요한 경우에는 바이너리 로그를 중지.
    • 바이너리 로그를 안정적으로 기록하기 위해 갭 락(Gap lock)을 유지하고, 매번 트랜잭션 이 커밋될 때 마다 데이터를 변경시킨 쿼리 문장을 바이너리 로그에 기록해야 한다. 바이너리 로그를 기록하는 작업은 AutoCommit이 활성화 된 MySQL 서버에서 심각한 부하로 나타날 때가 많다.
  • 바이너리 로그와 트랜잭션 격리 수준(Isolation level)
    • 바이너리 로그 파일은 어떤 내용이 기록되냐느에 따라 STATEMENT 포맷 방식과 ROW 포맷 방식이 있다.
    • STATEMENT 방식은 바이너리 로그 파일에 마스터에서 실행되는 쿼리 문장을 기록하는 방식이며, ROW 포맷은 마스터에서 실행된 쿼리에 의해 변경된 레코드 값을 기록하는 방식이다.
MySQL의 복제는 마스터에서 처리된 내용이 바이너리 로그로 기록되고, 그 내용이 슬레이브 MySQL 서버로 전달되어 재실행
되는 방식으로 처리된다. 바이너리 로그 파일에 SQL 문장을 기록하는 방식을 문장 기반 복제 라고 하며, 변경된 레코드를 바이너리 로그에
기록하는 방식을 레코드 기반의 복제 라고한다. SQL 기반의 복제가 정상적으로 작동하려면 REPEATABLE-READ 이상의 트랜잭션 격리 수준을
사용해야 하며, 그로 인해 InnoDB 테이블에서는 레코드 간의 간격을 잠그는 갭락이나 넥스트 키 락이 필요해진다. 반면 레코드 기반의 복제는 
마스터 슬레이브 MySQL 서버 간의 네티워크 트레픽을 많이 발생시킬 수 있지만 READ-COMMITTED 트랜잭션 격리 수준에서도 작동할 수 있으며 InnoDB 테이블에서 
잠금의 경합은 줄어든다.

쿼리캐시

6

여러가지 복잡한 처리 절차와 꽤 큰 비용을 들여 실행된 결과를 캐시에 담아 두고, 동일한 쿼리 요청이 왔을 때 간단하게
쿼리 캐시에서 찾아서 바로 결과를 내려 줄 수 있기 때문에 기대 이상의 효과를 거둘 수 있다.


즉, 쿼리의 결과를 메모리에 캐시해 두는 기능.

쿼리 캐시의 결과를 내려 보내주기 전에 반드시 다음과 같은 확인 절차를 거쳐야 한다.

  • 요청된 쿼리 문장이 쿼리 캐시에 존재하는가?
  • 해당 사용자가 그 결과를 볼 수 있는 권한을 가지고 있는가?
  • 트랜잭션 내에서 실행된 쿼리인 경우 가시 범위 내에 있는 결과인가?
    • 트랜잭션은 자신의 ID보다 ID 값이 큰 트랜잭션에서 변경한 작업 내역이나 쿼리 결과는 참조할 수 없다. 이를 가시범위 라고 한다. 쿼리 캐시도 그 결과를 만들어낸 트랜잭션의 ID가 가시 범위 내에 있을 떄만 사용할 수 있는 것이다.
  • CURRENT_DATE(), SYSDATE(), RAND() 등과 같이 호출 시점에 따라 결과가 달라지는 요소가 있는가?
  • 프리페어 스테이트먼트의 경우 변수가 결과에 영향을 미치지 않는가?
    • 프리페어 스테이트먼트(바인드 변수가 사용된 쿼리)의 경우에는 쿼리 문장 자체에 변수("?")가 사용되기 때문에 쿼리 문장 자체로 쿼리 캐시를 사용할 수 없다. 사용했다 하더라도 실제 MySQL 서버에서는 프리페어 스테이트먼트의 형태로 실행되지 않는다. 진정한 프리페어 스테이트먼트를 사용하려면 프로그램의 소스코드에서 데이터베이스 커넥션을 생성할 때 특별한 옵션을 사용해야만 한다.
  • 캐시가 만들어지고 난 이후 해당 데이터가 다른 사용자에 의해 변경되지 않았는가?
테이블에서 조횟수를 올리기 위해 SELECT한 결과를 쿼리 캐시에 저장했는데, 그 뒤에 실행된 UPDATE 쿼리의 의해 바로 쿼리 캐시에서 삭제 되는 현상이 
발생할 수 있다. 즉 이 테이블은 절대 쿼리 캐시를 사용할 수 없고,저장하고 삭제하는 오버헤드만 추가하는 꼴이 된다. 이런 경우
조회수 칼럼을 다른 테이블로 분리하거나 또는 조회수를 일정한 회수만큼 누적한 후 한꺼번에 업데이트하는 것이 좋다. 테이블 3개를 조인해서 하나의 
쿼리로 작성한 경우보다, 테이블별로 쿼리를 하나씩 쪼개서 작성한 경우가 3개의 테이블 가운데 하나가 변경돼도 3개의 쿼리
가운데 여전히 2개의 쿼리는 쿼리 캐시를 사용할 수 있다. 가끔은 복잡한 하나의 쿼리로 데이터를 가져오는 것보다 잘게 쪼게는 것이 
더 효율적일 때가 있다.
  • 쿼리에 의해 만들어진 결과가 캐시하기에 너무 크지 않은가?
    • 어떤 쿼리 하나가 너무 크면 하나의 쿼리 때문에 쿼리 캐시를 다 소모해 버릴 수 있다. 따라서 쿼리가 결과를 만들어내는 데 많은 시간과 자원이 필요하지만 만들어진 결과의 크기가 작을수록 쿼리 캐시를 더 효율적으로 사용할 수 있기 떄문에 GROUP BY나 DISTINCT, 그리고 COUNT()와 같은 집합 함수의 결과가 쿼리 캐시를 사용하기에 아주 적합하다.

InnoDB 스토리지 엔진 아키텍처

7

InnoDB는 MySQL에서 사용할 수 있는 스토리지 엔진 중에서 거의 유일하게 레코드 기반의 잠금을 제공하고 있으며, 때문에 높은 동시성 처리가 가능하고 또한 한정적이며 성능이 뛰어나다.

InnoDB 스토리지 엔진의 특성

  • 프라이머리 키에 의한 클러스터링
    • InnoDB의 모든 테이블은 기본적으로 PK를 기준으로 클러스터링 되어 저장됨. 즉, PK값 순서대로 디스크에
      저장되고, 이로 인해 PK에 의한 레인지 스캔은 상당히 빨리 처리된다.
  • 잠금이 필요 없는 일관된 읽기
    • InnoDB 스토리지 엔진은 MVCC라는 기술을 이용해 락을 걸지 않고 읽기 작업을 수행한다. 락을 걸지않기 때문에
      InnoDB에서 읽기 작업은 다른 트랜잭션이 가지고 있는 락을 기다리지도 않는다.
  • 외래키 지원
    • 외래키는 부모테이블이나 자식 테이블에 데이터가 있는지 체크하는 작업이 필요하므로 잠금이 여러 테이블로 전파되고,
      그로인해 데드락이 발생할 수 있다. 그래서 실무에서는 잘 사용하지 않는다.
  • 자동 데드락 감지
  • 자동화된 장애 복구
  • 오라클의 아키텍처 사용

InnoDB 버퍼풀

  • InnoDB 스토리지 엔진에서 가장 핵심적인 부분으로, 디스크의 데이터 파일이나 인덱스 정보를 메모리에 캐시해 두는 공간이다. 쓰기 작업을 지연시켜 일괄 작업으로 처리할 수 있게 해주는 버퍼역할도 같이 한다. 버퍼 풀이 변경된 데이터를 모아서 처리하게 되면 랜덤한 디스크 작업의 횟수를 줄일 수 있다.
  • 버퍼 풀의 크기는 innodb_buffer_pool_size로 설정하는데 일반적으로 전체 장착된 물리 메모리의 50 ~ 80% 수준에서 버퍼 풀의 메모리를 결정한다. 운영체제와 각 클라이언트 스레드가 사용할 메모리도 충분히 고려해서 결정해야 한다.
  • InnoDB 버퍼 풀은 아직 디스크에 기록되지 않은 변경된 데이터를 가지고 있다.(이러한 데이터를 가지고 있는 페이지를 더티페이지(Dirty page)라고 한다.)
  • 이러한 더티 페이지는 InnoDB에서 주기적으로 또는 어떤 조건이 되면 체크포인트 이벤트가
    발생하는데, 이떄 Write 스레드가 필요한 만큼의 데이터 페이지만 디스크로 기록한다

언두(Undo)로그

언두 영역은 UPDATE 문장이나 DELETE와 같은 문장으로 데이터를 변경해을 떄 변경되기 전의 데이터(이전 데이터)를 보관하는 곳이다.


벽계수에서 홍길동으로 변경했다고 했을때, 만약 사용자가 커밋하게 되면 현재 상태(홍길동)가 유지되고, 롤백하게 되면 언두 영역의 백업된 데이터를 다시 데이터 파일(데이터/인덱스 버퍼)로 복구한다.

 

언두의 데이터는 크게 두 가지 용도로 사용되는데,

  • 첫 번쨰 용도가 바로 위에서 언급한 트랜잭션의 롤백 대비용이다.
  • 두 번째 용도는 트랜잭션의 격리 수준을 유지하면서 높은 동시성을 제공하는 데 사용한다.

인서트 버퍼(Insert Buffer)

  • RDBMS에서 레코드가 INSERT되거나 UPDATE 될 떄는 데이터 파일을 변경하는 작업 뿐 아니라 해당 테이블에 포함된 인덱스를 업데이트하는 작업도 필요하다.
  • 그런데 인덱스를 업데이트하는 작업은 랜덤하게 디스크를 읽는 작업이 필요하므로 테이블에 인덱 스가 많다면 이 작업은 상당히 많은 자원을 소모하게 된다. 그래서 InnoDB는 변경해야 할 인덱스 페이지가 버퍼 풀에 있으면 업데이트를 수행하지만, 그렇지 않고 디스크로부터 읽어와서 업데이트해야 한다면 이를 즉시 실행하지 않고 임시 공간에 저장해 두고 바로 사용자에게 결과를 반환하는 형태로 성능을 향상 시키게 된다.
  • 이때 사용하는 임시 메모리 공간을 인서트 버퍼(Insert Buffer)라고 한다.
  • 사용자에게 결과를 전달하기 전에 반드시 중복 여부를 체크해야 하는 유니크 인덱스는 인서트 버퍼를 사용할 수 없다.
  • 인서트 버퍼에 임시로 저장돼 있는 인덱스 레코드 조각은 이후 백그라운드 스레드에 의해 병합되는데, 이 스레드를
    인서트 버퍼 머지 스레드(Merge thread라고 한다.
    ```text

5.5부터는 innod_change_buffering이라는 설정 파라미터가 새로 도입되어 작업의 종류별로 인서트 버퍼를 활성화할 수 있으며,
인서트 버퍼가 비효율적일 떄는 인서트 버퍼를 사용하지 않게 설정할 수 있도록 개선됐다.

### 리두(Redo) 로그 및 로그 버퍼
- 데이터를 버퍼링해 두기 위해 존재하는 InnoDB 버퍼 풀로만은 ACID를 보장할 수 없는데 이를 위해 변경된 내용을
순차적으로 디스크에 기록하는 로그 파일을 가지고 있다. 이것을 **리두 로그**라고 한다.
- **리두 로그** 덕분에 DBMS 데이터는 버퍼링을 통해 한꺼번에 디스크에 변경된 내용을 처리할 수 있고 그로 인해 상당한
성능 향상을 기대할 수 있게 됐다.
- 하지만 사용량(특히 변경 작업)이 매우 많은 DBMS 서버의 경우에는 이 리두 로그의 기록 작업이 큰 문제가 되는데, 이러한
부분을 보완하기 위해 ACID 속성을 보장하는 수준에서 버퍼링하게 된다. 이러한 리두 로그 버퍼링에 사용되는 공간이 **로그 버퍼**다.

```text
ACID는 데이터베이스에서 트랜잭션의 무결성을 보장하기 위해 반드시 필요한 4가지 요소(기능)를 의미한다.
"A"는 Atomic의 첫글자로, 트랜잭션은 원자성 작업이어야 함을 의미한다.
"C"는 Consistent의 첫글자로, 일관성을 의미한다.
"I"는 Isolated 에서 온 첫 글자로, 격리성을 의미한다.
"D"는 Durable의 첫 글자이며, 한번 저장된 데이터는 지속적으로 유지돼야 함을 의미한다.

일관성과 격리성은 쉽게 정의하기는 힘들지만, 이 두 가지 속성은 서로 다른 두 개의 트랜잭션에서 동일 데이터를 조회
하고 변경하는 경우에도 상호 간섭이 없어야 한다는 것을 의미한다.

MVCC(Multi Version Concirrency Control)

일반적으로 레코드 레벨의 트랜잭션을 지원하는 DBMS가 제공하는 기능이며, MVCC의 가장 큰 목적은 잠금을 사용하지
않는 일관된 읽기
를 제공하는데 있다. InnoDB는 언두 로그를 이용해 이 기능을 구현한다.

 

예를 들어 특정 데이터 레코드를 1번 사용자가 'A' 내용에서 'B' 내용으로 UPDATE 한다고 가정해 보자.


이 때의 상황은 다음과 같다.

  • InnoDB 버퍼 풀 : 수정된 'B' 내용이 반영되어 있다. (레코드 전체)
  • Undo 로그 : 수정 전의 'A' 내용이 반영되어 있다. (PK, 메타정보 및 수정된 칼럼만 백업)
    이 때 커밋이나 롤백이 아직 일어나지 않는 상황에서, 2번 사용자가 해당 데이터를 읽으려고 하면 어떻게 될까?

이 질문의 답은 격리 수준에 따라 다르다.

  • READ_UNCOMMITTED : InnoDB 버퍼 풀이나 데이터 파일로부터 변경된 'B' 데이터를 읽어서 반환한다.
  • READ_COMMITTED 이상 : 아직 커밋되지 않았기 때문에 언두 영역의 'A' 데이터를 반환한다.
    이러한 과정을 DBMS 에서는 MVCC 라고 표현한다.

잠금 없는 일관된 일기(Non-locking consistent read)

  • InnoDB에서 격리 수준이 SERIALIZABLE이 아닌 READ-UNCOMMITTED나 READ-COMMITTED 그리고 REPEATABLE_READ 수준인 경우 INSERT와 연결되지 않은 순수한 읽기(SELECT) 작업은 다른 트랜잭션의 변경 작업과 관계없이 항상 잠금을 대기하지 않고 바로 실행된다.
  • 읽으려는 행이 실제로 lock이 걸려있어도 읽을 때는 undo영역에서 읽기 때문에 lock이 걸리든 말든 상관없이 이전 버전의 데이터를 읽을 수 있다.

참고 문헌 :

개발자외 DBA를 위한 Real MySQL / 이성민

728x90
반응형
Comments