메인 콘텐츠로 건너뛰기

VillageSQL is a drop-in replacement for MySQL with extensions.

All examples on this page work on VillageSQL. Install Now →
InnoDB는 MySQL의 기본 스토리지 엔진입니다. InnoDB가 데이터를 저장하는 방식을 이해하면 어떤 쿼리는 빠르고 어떤 쿼리는 그렇지 않은 이유가 설명됩니다 — 특히 기본 키 선택이 중요한 이유, 범위 스캔이 무작위 조회보다 나은 이유, 큰 테이블의 인덱스가 쓰기 성능을 해칠 수 있는 이유가 그렇습니다.

페이지: 저장의 단위

InnoDB는 모든 것을 페이지에 저장합니다 — 기본적으로 16KB의 고정 크기 덩어리입니다. 페이지는 InnoDB가 디스크에서 읽거나 디스크에 쓰는 가장 작은 단위입니다. 행 하나를 읽을 때 InnoDB는 그 행이 들어 있는 16KB 페이지 전체를 로드합니다. 이것은 실질적인 결과를 낳습니다:
  • 연속된 행을 읽는 쿼리(범위 스캔, 정렬된 데이터에 대한 전체 테이블 스캔)는 각 페이지를 한 번 로드하고 그 페이지에서 많은 행을 처리합니다
  • 무작위 행을 읽는 쿼리(큰 테이블에 대한 보조 인덱스 사용)는 행마다 새 페이지를 로드하며, 각각이 별도의 디스크 읽기일 가능성이 큽니다

클러스터형 인덱스

InnoDB는 테이블 행을 클러스터형 인덱스에 저장합니다 — 리프 노드에 실제 행 데이터가 들어 있고 기본 키로 물리적으로 정렬된 B-트리입니다. 별도의 “테이블 파일”과 “기본 키 인덱스 파일”은 없습니다 — 둘은 같은 구조입니다.
이 테이블의 행은 디스크에 id 순서로 물리적으로 저장됩니다. WHERE id BETWEEN 1000 AND 2000 같은 쿼리는 연속된 페이지 범위를 읽습니다 — 빠릅니다. id = 1, 그다음 id = 500000, 그다음 id = 250인 행을 가져오는 쿼리는 물리적으로 흩어진 세 페이지를 읽습니다. 기본 키를 정의하지 않으면 InnoDB는 숨겨진 6바이트 행 ID를 만들어 클러스터형 인덱스 키로 사용합니다. 이것은 최적화 기회를 낭비합니다.

보조 인덱스

모든 보조 인덱스는 별도의 B-트리입니다. 리프 노드는 전체 행을 저장하지 않습니다 — 인덱싱된 열 값과 기본 키를 저장합니다. 보조 인덱스에 없는 열을 가져오기 위해 InnoDB는 행 조회를 합니다: 보조 인덱스 리프에서 기본 키를 가져와 클러스터형 인덱스에서 전체 행을 가져옵니다.
이 두 단계 과정이 커버링 인덱스가 중요한 이유입니다: 보조 인덱스가 쿼리에 필요한 모든 열을 포함하면 행 조회를 건너뜁니다. MySQL의 커버링 인덱스를 참조하세요. 행 조회 비용은 큰 기본 키가 비싼 이유이기도 합니다. 기본 키가 INT(4바이트) 대신 UUID 문자열(36바이트)이면, 모든 보조 인덱스가 항목당 4바이트 대신 36바이트를 저장하고, 행 조회는 더 깊은 B-트리를 탐색합니다.

버퍼 풀

InnoDB는 페이지를 버퍼 풀이라는 메모리에 캐시합니다. 디스크에서 읽은 페이지는 버퍼 풀에 로드되어 다른 것에 메모리가 필요해질 때까지 거기에 머뭅니다. 같은 페이지에 대한 이후 읽기는 RAM에서 처리됩니다.
전용 데이터베이스 서버에서는 innodb_buffer_pool_size를 사용 가능한 RAM의 70–80%로 설정하세요. 이것은 읽기 위주 워크로드에서 가장 영향력이 큰 단일 구성 변경입니다 — 작업 집합이 메모리에 많이 들어갈수록 디스크 읽기가 줄어듭니다.

행 형식

InnoDB는 네 가지 행 형식을 지원합니다. 기본값은 DYNAMIC입니다. 기본 16KB 페이지의 대략 절반인 ~8KB 수치는 InnoDB가 가변 길이 열 데이터를 페이지 외부로 옮기는 지점이므로, 큰 VARCHAR/TEXT/BLOB 열이 몇 개뿐인 행은 상한에 부딪히지 않고 그것을 훨씬 넘어 커질 수 있습니다. 그러나 페이지 외부로 옮겨지지 않고 인라인으로 저장되는 열 — 예를 들어 많은 고정 폭 CHAR 열 — 은 여전히 InnoDB 자체의 행 크기 제한에 전부 계산되며, 그 제한에 도달하면 ERROR 1118 (42000): Row size too large (> 8126). Changing some columns to TEXT or BLOB may help.가 발생합니다. MySQL의 별도이고 더 높은 행 크기 제한은 BLOB/TEXT 오버플로 페이지를 제외하고 65,535바이트이며, ROW_FORMAT과 관계없이 적용됩니다. 두 제한 모두 실제이며, 어떤 열이 크고 그것이 페이지 외부로 옮겨질 수 있는지에 따라 어느 쪽이든 먼저 부딪히는 제한이 될 수 있습니다. DYNAMIC 형식에서 VARCHAR, TEXT 또는 BLOB 열이 페이지를 넘치면 전체 값이 오버플로 페이지에 저장됩니다 — 메인 행에는 20바이트 포인터만 남습니다. COMPACT는 대신 처음 768바이트를 인라인으로 저장합니다. DYNAMIC의 접근 방식은 긴 값이 있을 때 메인 행을 더 작게 유지하므로, 페이지당 더 많은 행이 들어가고 B-트리가 더 얕게 유지됩니다.

자주 묻는 질문

기본 키 선택이 왜 성능에 그렇게 큰 영향을 주나요?

클러스터형 인덱스가 행을 기본 키로 물리적으로 정렬하기 때문입니다. 무작위 위치에 삽입(UUID 기본 키 같은)하면 페이지 분할이 일어납니다 — InnoDB는 순서를 유지하기 위해 가득 찬 페이지를 분할하고 데이터를 옮겨야 합니다. 순차적 기본 키(AUTO_INCREMENT, UUID v7)는 끝에 삽입되어 분할을 피합니다. 페이지 분할은 테이블스페이스를 단편화하고 시간이 지남에 따라 쓰기와 읽기를 모두 느리게 합니다.

버퍼 풀이 너무 작으면 어떻게 되나요?

InnoDB는 디스크에서 페이지를 자주 읽어야 합니다. Innodb_buffer_pool_reads가 올라가는 것을 보게 됩니다. 서버는 I/O 바운드가 됩니다 — RAM에서라면 마이크로초가 걸릴 쿼리가 디스크에서는 밀리초가 걸립니다. 읽기 위주 워크로드에서는 작업 집합을 담는 버퍼 풀이 거의 어떤 인덱스 최적화보다 가치 있습니다.

InnoDB와 MyISAM의 차이는 무엇인가요?

InnoDB는 트랜잭션, 외래 키, 행 수준 잠금, 충돌 복구를 지원합니다. MyISAM은 이 중 어느 것도 지원하지 않습니다 — 특정 읽기 전용 대량 워크로드에는 더 빠르지만 대부분의 애플리케이션에는 적합하지 않습니다. InnoDB가 MySQL 기본값이며, 특별한 이유가 없다면 그것을 사용하세요.

문제 해결

VillageSQL은 같은 InnoDB 엔진을 탑재하므로, 이 페이지의 모든 설정이 그대로 적용됩니다. VillageSQL을 설치하여 InnoDB를 유지하면서 사용자 정의 타입, 함수, 인덱스를 위한 확장 프레임워크를 얻으세요.

참고