RealMySQL 8.0 을 읽고 정리했습니다.
InnoDB 스토리지 엔진 아키텍처
InnoDB → MySQL의 스토리지 엔진 가운데
- 가장 많이 사용되고,
- 거의 유일하게 레코드 기반의 잠금을 제공하여 동시성 처리가 가능하고, 안정적이며 뛰어난 성능
- 구조

4.2.1 프라이머리 키에 의한 클러스터링
- InnoDB의 모든 테이블은 기본적으로 프라이머리 키를 기준으로 클러스터링 되어 저장 → 즉, 프라이머리 키 값의 순서대로 디스크에 저장되어 → 프라이머리 키에 의한 스캔은 상당히 빨리 처리됨
- 결과적으로 쿼리의 실행 계획에서 프라이머리 키는 다른 보조 인덱스에 비해 높은 비중으로 설정
- InnoDB 테이블 구조 = 오라클의 IOT(Index Organized Table) 구조
4.2.2 외래 키 지원
- Only InnoDB, not MyISAM과 MEMORY 테이블
- 여러가지 제약사항이 있어 실무에서는 잘 사용하지 않음
- InnoDB의 외래 키는 부모 테이블과 자식 테이블 모두 해당 칼럼에 인덱스 생성이 필요하고, 변경 시에는 반드시 부모 테이블이나 자식 테이블에 데이터가 있는지 체크하는 작업이 필요하므로 잠금이 여러 테이블로 전파되고, 그로 인해 데드락이 발생할 때가 많아서 실무에서 잘 사용하지 않는다.
4.2.3 MVCC(Multi Version Concurrency Control)
- 일반적으로 레코드 레벨의 트랜잭션을 지원하는 DBMS가 제공하는 기능
- MVCC의 가장 큰 목적은 잠금 없는 일관된 읽기를 제공하는 것 → InnoDB는 언두 로그를 이용해 이 기능을 구현함
- ex) ‘유재석’에서 ‘홍길동’으로 UPDATE
mysql > UPDATE member SET name='홍길동' WHERE member_id='1';
이 때의 상황
- InnoDB 버퍼 풀 : 수정 후 ‘홍길동'이 반영됨 (레코드 전체)
- 언두 로그 : 수정 전 ‘유재석'이 반영됨 (PK, 메타정보 및 수정된 칼럼만 백업)
→ 이 때, 커밋이나 롤백이 일어나지 않은 상황에서 2번 사용자가 해당 데이터를 읽으려고 하면 어떻게 될까?
결론 : 격리 수준에 따라 다르다.
- READ_UNCOMMITTED : InnoDB 버퍼 풀이나 데이터 파일로부터 변경된 ‘홍길동' 데이터를 읽어서 반환한다.
- READ_COMMITTED 이상 : 아직 커밋되지 않았기 때문에 언두 영역의 '유재석' 데이터를 반환한다. 이러한 과정을 DBMS 에서는 MVCC 라고 표현한다.
4.2.4 잠금 없는 일관된 읽기(Non-Locking Consistent Read)
- InnoDB 스토리지 엔진은 MVCC를 이용해 INSERT와 연결되지 않은 순수한 SELECT 작업은 락을 걸지 않고, 바로 수행
- 락을 걸지 않기 때문에 다른 트랜잭션이 갖고 있는 락을 기다리지 않음
- 읽기 작업 가능(serializable 격리 수준은 제외)
- lock이 걸려있어도 읽을 때는 언두 영역에서 읽기 때문에 lock이 걸리든 말든 상관없이 이전 버전의 데이터를 읽을 수 있다.
4.2.5 자동 데드락 감지
- InnoDB는 그래프 기반의 데드락 체크 방식을 사용 → 데드락이 발생함과 동시에 바로 감지되고, 감지된 데드락은 관련 트랜잭션 중에서 ROLLBACK이 가장 용이한 트랜잭션(ROLLBACK을 했을 때 복구 작업이 가장 작은 트랜잭션, 즉 레코드를 가장 적게 변경한 트랜잭션)을 자동적으로 강제 종료해 버린다. 따라서 데드락 때문에 쿼리가 제한시간(Timeout)에 도달하거나 슬로우 쿼리로 기록되는 경우는 많지 않다.
4.2.6 자동화된 장애 복구
- InnoDB는 손실이나 장애로부터 데이터를 보호하기 위한 여러 가지 매커니즘이 탑재!
- MySQL 서버가 시작될 때, 완료되지 못한 트랜잭션이나 디스크에 일부만 기록된 데이터 페이지(Partial write) 등에 대한 일련의 복구 작업이 자동으로 진행된다.
4.2.7 InnoDB 버퍼 풀
- InnoDB에서 가장 핵심적인 부분
- innodb_buffer_pool_size 로 설정, 전체 물리 메모리의 50~80% 수준으로 설정
- 디스크의 데이터 파일이나 인덱스 정보를 메모리에 캐시해 두는 공간 + 쓰기 작업을 지연시켜 일괄 작업으로 처리할 수 있게 해주는 버퍼 역할 → INSERT나 UPDATE 그리고 DELETE와 같이 데이터를 변경하는 쿼리는 디스크 작업을 발생시킴 → 버퍼 풀이 있다면 이러한 변경된 데이터를 모아서 처리하게 되면 랜덤한 디스크 작업의 횟수를 줄일 수 있다!
- 아직 디스크에 기록되지 않은 변경된 데이터를 가지고 있다. → 이러한 데이터를 가지고 있는 페이지를 더티 페이지(Dirty page)
- 더티 페이지는 InnoDB에서 주기적으로 또는 어떤 조건이 되면 체크포인트 이벤트가 발생하는데, 이때 Write 스레드가 필요한 만큼의 더티 페이지만 디스크로 기록한다. 체크포인트가 발생한다고 해서 버퍼 풀의 모든 더티 페이지를 디스크로 기록하는 것은 아니다.
4.2.8 Double Write Buffer
- 파셜 페이지(Partial-page) or 톤 페이지(Ton-page) : 페이지가 일부만 기록되는 현상, 하드웨어 오작동이나 시스템 비정상 종료 등으로 발생함
- InnoDB의 리두 로그는 공간의 낭비를 막기 위해 페이지의 변경된 내용만을 기록함 → 이로 인해 InnoDB에서 더티 페이지를 디스크 파일로 플러시할 때 일부만 기록되는 문제가 발생하면 해당 페이지는 복구가 어려울 수도.. → 이를 막기 위해 Double Write 기법을 이용!
4.2.9 언두(Undo) 로그
- ex) ‘유재석’에서 ‘홍길동’으로 UPDATE
mysql > UPDATE member SET name='홍길동' WHERE member_id='1';
- 언두 영역 : when? UPDATE나 DELETE 문장으로 데이터를 변경했을 때 why? 변경되기 전의 데이터를 보관하고자 사용
- 언두 데이터 : ex) 언두 데이터 ‘유재석’ why?
- 트랜잭션의 rollback 대비용
- 트랜잭션의 격리 수준을 유지하면서 높은 동시성 제공해줘서
- 트랜잭션의 격리 수준 : 동시에 여러 트랜잭션이 데이터를 변경하거나 조회할 때, 한 트랜잭션의 작업 내용이 다른 트랜잭션에 어떻게 보여질지를 결정하는 기준
4.2.10 체인지 버퍼
- 인덱스 변경에 따른 자원 소모를 줄여주는 임시 메모리 공간
탄생 배경
- 🧐 문제 : INSERT나 UPDATE 될 때 데이터 파일을 변경하는 작업 + 해당 테이블에 포함된 인덱스를 변경하는 작업 이 필요 →해당 테이블에 포함된 인덱스를 변경하는 작업 은 랜덤하게 디스크를 읽는 작업이 필요하므로 테이블에 인덱스가 많다면 상당히 많은 자원을 소모
- 😃 해결 : 인덱스를 변경해야 한다면, 즉시 실행하지 않고 임시 공간에 저장해 두고 바로 사용자에게 반환하는 형태로 성능을 향상! → 이 때 사용하는 임시 메모리 공간 = 체인지 버퍼
특징
- 반드시 중복 여부를 체크해야 하는 유니크 인덱스는 체인지 버퍼를 사용할 수 없음
- 체인지 버퍼에 임시로 저장된 인덱스 레코드 조각은 이후 백그라운드 스레드에 의해 병합됨 → 이를
체인지 버퍼 머지 스레드라고 함
4.2.11 리두(Redo) 로그 및 로그 버퍼
데이터 파일을 변경하는 것은 랜덤하게 디스크에 기록해야 하므로 비용이 비싼 작업이다. → 이 작업을 모아서 처리하여 성능을 향상하기 위해 InnoDB 버퍼 풀 같은 장치가 있음
BUT! InnoDB 버퍼 풀 만으로는 ACID를 보장할 수 없어서 → 순차적으로 디스크에 기록하는 로그 파일을 생성 = 리두 로그 (일반적으로 로그를 지칭함) 너무 많은 변경이 일어나면 리두 로그에 기록하는 것도 큰 문제가 되어서 보완하고자 = 로그 버퍼 로그 버퍼의 크기 = 일반적으로 1~8MB 수준, BLOB나 TEXT와 같이 큰 데이터를 자주 변경하는 경우는 더 크게 설정
4.2.12 어댑티브 해시 인덱스
- InnoDB에서 사용자가 자주 요청하는 데이터에 대해 자동으로 생성하는 인덱스
- innodb_adaptive_hash_index : 시스템 변수를 이용해서 활성화 및 비활성화
도입 목적
- B-Tree의 검색 시간을 줄여주기 위해
작동 방식
- 자주 읽히는 데이터 페이지의 키 값을 이용해 변수 생성
- 필요할 때마다 어댑티브 해시 인덱스를 검색해서 레코드가 저장된 데이터 페이지로 가기
- → 장점! B-Tree를 루트 노드부터 리프 노드까지 찾아가는 비용이 없어지고, CPU는 적은 일을 하지만 쿼리 성능은 빨라져서 동시에 더 많은 쿼리를 처리함
특징
- (key, value) = (’인덱스 키 값’, 해당 인덱스 키 값이 저장된 ‘데이터 페이지 주소’)
- 인덱스 키 값 = ‘B-Tree 인덱스 고유번호(ID)와 B-Tree 인덱스 실제 키 값’ 조합
- 인덱스 키 값에 ‘B-Tree 인덱스의 고유번호’가 포함되는 이유 → InnoDB에서 어댑티브 해시 인덱스는 하나만 존재하기 때문이다. 즉, 특정 키 값이 어느 인덱스에 속한 것인지 구분하기 위해서!