MySQL 서버는 크게 MySQL 엔진과 스토리지 엔진으로 구성되어 있다.
MySQL 엔진은 요청된 SQL문을 분석하고 최적화하는 등 데이버테이스의 두뇌에 해당하는 역할을 하고, 실제 데이터를 디스크에 저장하거나 데이터를 읽어오는 작업은 스토리지 엔진이 담당한다.
MySQL 엔진은
등으로 이루어져 있다.
MySQL 엔진의 쿼리 실행기에서 데이터를 쓰거나 읽어야 할 때 스토리지 엔진에 보내는 요청을 핸들러 요청이라 하고, 이 때 사용되는 API를 핸들러 API 라고 한다.
즉, MySQL 엔진과 스토리지 엔진은 핸들러 API를 통해 데이터를 주고 받는다.
InnoDB 스토리지 엔진은 MySQL에서 가장 많이 사용되는 스토리지 엔진이다. MySQL의 스토리지 엔진 중에서 거의 유일하게 레코드 기반 잠금을 제공하며, 때문에 높은 동시성 처리가 가능하고 안정적이며 성능이 뛰어나다.
MVCC는 하위 레코드에 대해 여러 개의 버전이 동시에 관리된다는 의미이며, 시스템에서 설정한 트랜잭션 격리 수준에 따라 다르게 처리된다.
예를 들어, 다음과 같이 테이블에 한 건의 레코드를 변경한다면
UPDATE member
SET m_area = '경기'
WHERE m_id = 12;
InnoDB 의 버퍼 풀과 데이터 파일엔 다음과 같이 기록되어 있다.
아직 커밋이나 롤백되지 않은 상태에서 다른 사용자가 해당 레코드를 조회한다면..
격리 수준이 READ_UNCOMMITTED 라면 -> 버퍼 풀이 현재 가지고 있는 변경된 데이터를 읽어서 반환한다.
그 이상의 격리 수준이라면 (READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE) -> 아직 커밋되지 않았기 때문에 변경 이전의 내용을 보관하고 있는 언두 영역의 데이터를 반환한다.
앞에서 살펴본 MVCC 기술을 이용해 잠금을 걸지 않고 읽기 작업을 수행한다.
격리 수준이 SERIALIZABLE 이 아니라면 순수한 읽기(SELECT) 작업은 다른 트랜잭션의 변경 작업과 관계없이 항상 잠금을 대기하지 않고 바로 실행된다.
다음과 같이 특정 사용자가 레코드를 변경하고 아직 커밋하지 않아도, 이 변경 트랜잭션이 다른 사용자의 SELECT 작업을 방해하지 않는다. 이를 잠금 없는 일관된 읽기 라고 표현하며, InnoDB에서는 변경 전 데이터를 읽기 위해 언두 로그를 사용한다.
내부적으로 잠금이 교착 상태에 빠지지 않았는지 체크하기 위해 잠금 대기 목록을 그래프 형태로 관리한다.
데드락 감지 스레드를 통해 주기적으로 잠금 대기 그래프를 검사해 교착 상태에 빠진 트랜잭션들을 찾아서 그 중 하나를 강제 종료한다.
이때, 언두 로그를 더 적게 가진 트랜잭션을 먼저 종료한다. (언두 레코드가 적다 = 롤백할 때 언두를 처리할 내용이 적다 -> 트랜잭션 강제 롤백으로 인한 서버의 부하도 덜 하다)
InnoDB에는 손실이나 장애로부터 데이터를 보호하기 위한 여러 메커니즘이 탑재되어 있다.
이러한 메커니즘을 활용해 MySQL 서버가 시작될 때 완료되지 못한 트랜잭션이나 디스크에 일부만 기록되 데이터 페이지 등에 대한 복구 작업이 자동으로 진행된다.
자동으로 복구될 수 없는 손상이 있다면 자동 복구를 멈추고 MySQL 서버가 종료된다.
InnoDB 스토리지 엔진에서 가장 핵심적인 부분으로, 디스크의 데이터 파일이나 인덱스 정보를 메모리에 캐시해 두는 공간이다.
쓰기 작업을 지연시켜 일괄 작업으로 치리할 수 있게 하는 버퍼 역할도 같이 한다.
트랜잭션과 격리 수준을 보장하기 위해 DML(INSERT, UPDATE, DELETE)로 변경되기 이전 버전의 데이터를 별도로 백업해둔다. 이렇게 백업된 데이터를 언두 로그(Undo Log) 라고 한다.
언두 로그는 InnoDB 에서 매우 중요한 역할을 담당하지만 관리 비용도 많이 필요하다.
레코드가 INSERT 되거나 UPDATE 될 때는 데이터 파일을 변경하는 작업 + 해당 테이블에 포함된 인덱스를 업데이트하는 작업이 필요하다. 그런데 인덱스를 업데이트하는 작업은 랜덤하게 디스크를 읽는 작업이 필요하므로 테이블에 인덱스가 많다면 이 작업은 상당히 많은 자원을 소모하게 된다.
InnoDB 는 변경해야 할 인덱스 페이지가 버퍼 풀에 있으면 바로 업데이트를 수행하지만 디스크로부터 읽어와서 업데이트해야 한다면 이를 즉시 실행하지 않고 임시 공간에 저장해 두고 바로 사용자에게 결과를 반환하는 형태로 성능을 향상시키는데 이때 사용하는 임시 메모리 공간을 체인지 버퍼(Change Buffer)라고 한다.
MySQL 서버가 비정상적으로 종료되었을 때 데이터 파일에 기록되지 못한 데이터를 잃지 않게 해주는 안전장치 역할을 한다.
사용자가 직접 설정한 인덱스가 아닌 InnoDB 에서 사용자가 자주 요청하는 데이터에 대해 자동으로 생성하는 인덱스이다.
B-Tree의 검색 시간을 줄이기 위해 도입된 기능이다. 자주 읽히는 데이터 페이지의 키 값을 이용해 해시 인덱스를 만들고 필요할 때마다 어댑티브 해시 인덱스를 검색해 레코드가 저장된 데이터 페이지를 즉시 찾아갈 수 있게 해 준다. (B-Tree 처럼 루트 노드부터 리프 노드까지 찾아가는 비용이 없어지고 쿼리 성능이 빨라짐)
어뎁티브 해시 인덱스는 버퍼 풀에 올려진 데이터 페이지에 대해서만 관리되고, 버퍼 풀에서 해당 데이터 페이지가 없어지면 어댑티브 해시 인덱스에서도 해당 페이지의 정보는 사라진다.
성능 향상에 크게 도움이 되지 않는 경우
성능 향상에 도움이 되는 경우
Reference
Real MySQL 8.0 (1권)