항상 MySQL을 사용하면서 전반적인 구조가 궁금했다. 두루뭉실하게 알았던 내용을 Real MySQL을 토대로 정리하고자 한다. MySQL 서버는 사람의 머리 역할을 담당하는 MySQL 엔진과 손발 역할을 담당하는 스토리지 엔진으로 구분된다.

MySQL 엔진
클라이언트로부터 접속 및 쿼리 요청을 처리하는 커넥션 핸들러와 SQL 파서 및 전처리기, 쿼리의 최적화된 실행을 위한 옵티마이저가 중심을 이룬다.
스토리지 엔진
MySQL 엔진은 요청된 SQL 문장을 분석하거나 최적화하는 등 DBMS의 두뇌에 해당하는 처리를 수행하고, 실제 데이터를 디스크 스토리지에 저장하거나 디스크 스토리지로부터 데이터를 읽어오는 부분은 스토리지 엔진이 담당한다.

스레딩 구조
MySQL 서버는 프로세스 기반이 아니라 스레드 기반으로 동작한다.
포그라운드 스레드
포그라운드 스레드는 최소한 MySQL 서버에 접속된 클라이언트의 수만큼 존재하며, 주로 각 클라이언트 사용자가 요청하는 쿼리 문장을 처리한다. 클라이언트 사용자가 작업을 마치고 커넥션을 종료하면 해당 커넥션을 담당하던 스레드는 다시 스레드 캐시로 돌아간다. 포그라운드 스레드는 데이터를 MySQL의 데이터 버퍼나 캐시로부터 가져오며, 버퍼나 캐시에 없는 경우에는 직접 디스크의 데이터나 인덱스 파일로부터 데이터를 읽어와서 작업을 처리한다. MyISAM 테이블은 디스크 쓰기 작업까지 포그라운드 스레드가 처리하지만 InnoDB 테이블은 데이터 버퍼나 캐시까지만 포그라운드 스레드가 처리하고, 나머지 버퍼로부터 디스크까지 기록하는 작업은 백그라운드 스레드가 처리한다.
백그라운드 스레드
InnoDB에서는 여러 가지 작업을 백그라운드 스레드에서 진행한다.
- 인서트 버퍼를 병합하는 스레드
- 로그를 디스크로 기록하는 스레드
- InnoDB 버퍼 풀의 데이터를 디스크에 기록하는 스레드
- 데이터를 버퍼로 읽어오는 스레드
- 잠금이나 데드락을 모니터링하는 스레드
모두 중요하지만 가장 중요한 것은 로그 스레드와 버퍼의 데이터를 디스크로 내려쓰는 작업을 처리하는 쓰기 스레드일 것이다. InnoDB가 백그라운드에서 작업을 처리하는 이유 InnoDB가 이러한 작업들을 백그라운드에서 처리하는 가장 큰 이유는 쿼리 처리 성능 향상을 위해서다. 만약 사용자 쿼리를 처리하는 포그라운드 스레드가 디스크 I/O까지 직접 처리한다면, 디스크의 느린 쓰기 속도 때문에 사용자는 쿼리 결과를 받기까지 오래 기다려야 한다.
InnoDB는 이를 해결하기 위해 변경된 데이터를 우선 메모리(버퍼 풀)에만 기록하고 사용자에게 빠르게 응답한다. 그 후 백그라운드 스레드가 여유 있을 때 메모리의 변경 내용을 디스크로 기록하는 것이다. 또한 여러 변경 사항을 모아서 한 번에 처리하거나(Write Combining), 병렬로 처리할 수 있어 전체적인 I/O 효율도 높아진다. 대용량 데이터 처리 시에는 이러한 비동기 처리와 병렬 처리의 효과가 더욱 두드러진다.

메모리 할당 및 사용 구조
MySQL에서 사용되는 메모리 공간은 크게 글로벌 메모리 영역과 로컬 메모리 영역으로 구분할 수 있다. 글로벌 메모리 영역의 모든 메모리 공간은 MySQL 서버가 시작되면서 운영체제로부터 할당된다.
글로벌 메모리 영역
일반적으로 클라이언트 스레드의 수와 무관하게 하나의 메모리 공간만 할당된다. 필요에 따라 2개 이상의 메모리 공간을 할당받을 수도 있지만 스레드 수와 무관하며, 글로벌 영역이 N개라도 모든 스레드에 의해 공유된다.
- 테이블 캐시: 테이블의 메타데이터 정보를 캐싱하여 테이블 오픈 작업의 오버헤드를 줄이는 영역이다.
- InnoDB 버퍼 풀: 디스크의 데이터 파일이나 인덱스 정보를 메모리에 캐싱해두는 공간으로, InnoDB 성능에 가장 큰 영향을 미치는 핵심 메모리 영역이다.
- InnoDB 어댑티브 해시 인덱스: 자주 읽히는 데이터 페이지의 키 값을 이용해 InnoDB가 자동으로 생성하는 해시 인덱스로, B-Tree 검색 시간을 줄여준다.
- InnoDB 리두 로그 버퍼: 트랜잭션의 변경 내용을 순차적으로 기록하는 리두 로그를 버퍼링하는 공간이다.
로컬 메모리 영역
세션 메모리 영역이라고도 하며, MySQL 서버상에 존재하는 클라이언트 스레드가 쿼리를 처리하는 데 사용하는 메모리 영역이다.
커넥션 버퍼는 클라이언트와 MySQL 서버 간의 네트워크 통신을 위한 버퍼로, 쿼리 요청을 받거나 결과를 전송할 때 사용된다. 소트 버퍼는 쿼리의 정렬 작업이 필요할 때 사용되는 메모리 영역으로, ORDER BY나 GROUP BY 절 처리 시 옵티마이저가 정렬을 수행할 때 활용된다.
로컬 메모리는 각 클라이언트 스레드별로 독립적으로 할당되며 절대 공유되어 사용되지 않는다는 특징이 있다. 또한 각 쿼리의 용도별로 필요할 때만 공간이 할당되고, 그렇지 않은 경우에는 MySQL이 메모리 공간을 할당조차 하지 않을 수 있다.
- 정렬 버퍼: ORDER BY나 GROUP BY 작업을 메모리에서 처리하기 위한 공간이다.
- 조인 버퍼: 조인되는 테이블 간의 레코드를 임시로 보관하는 메모리 영역이다.
- 바이너리 로그 캐시: 트랜잭션의 변경 내용을 바이너리 로그에 기록하기 전에 임시로 저장하는 메모리 공간이다.
- 네트워크 버퍼: 클라이언트로 결과를 전송하거나 요청을 받을 때 사용하는 네트워크 통신용 버퍼다.

플러그인 스토리지 엔진 모델
MySQL의 강력한 기능 중 하나는 플러그인 아키텍처다. 전문 검색 엔진을 위한 검색어 파서(인덱싱할 키워드를 분리해내는 작업)도 플러그인 형태로 개발해서 사용할 수 있으며, 사용자의 인증을 위한 Native Authentication과 Caching SHA-2 Authentication 등도 모두 플러그인으로 구현되어 제공된다.
이러한 플러그인 구조 덕분에 MySQL 서버의 핵심 기능을 변경하지 않고도 필요한 기능을 추가하거나 교체할 수 있다. 특히 스토리지 엔진을 선택하거나 변경할 때 애플리케이션 코드의 수정 없이 테이블 단위로 다른 엔진을 사용할 수 있어 유연성이 매우 높다.
MySQL에서 핸들러라는 것은 개념적인 내용이라서 완전히 이해하지 못하더라도, 각 스토리지 엔진에게 데이터를 읽어오거나 저장하도록 명령하려면 반드시 핸들러를 통해야 한다는 점을 꼭 기억하자.
MySQL 서버에서 지원되는 스토리지 엔진은 플러그인으로 설치할 수 있고, 업데이트도 편하게 가능하다. 서버 재시작 없이 동적으로 스토리지 엔진을 추가하거나 제거할 수 있어 운영 중인 서비스에 영향을 최소화할 수 있다.
MySQL 8.0부터는 기존 플러그인 아키텍처를 개선한 컴포넌트 아키텍처가 도입되었다. 예를 들어 비밀번호 검증 기능이 플러그인에서 컴포넌트로 개선되었는데, 컴포넌트는 플러그인보다 더 명확한 인터페이스와 서비스 기반 구조를 가지고 있다. 이를 통해 각 기능 간의 의존성을 더 명확히 관리할 수 있고, 더 안정적이고 확장 가능한 구조를 제공한다. 또한 컴포넌트는 표준화된 방식으로 다른 컴포넌트의 서비스를 사용할 수 있어 기능 간 통합이 더욱 용이해졌다.
쿼리 실행 구조

쿼리 파서
사용자 요청으로 들어온 쿼리 문장을 토큰(MySQL이 인식할 수 있는 최소 단위의 어휘나 기호)으로 분리해 트리 형태의 구조로 만들어 내는 작업을 의미한다. 쿼리 문장의 기본 문법 오류는 이 과정에서 발견되고 사용자에게 오류 메시지를 전달한다.
전처리기
파서 과정에서 만들어진 파서 트리를 기반으로 쿼리 문장에 구조적인 문제점이 있는지 확인한다. 각 토큰을 테이블 이름이나 칼럼 이름, 또는 내장 함수와 같은 개체에 매핑해 존재 여부 및 접근 권한을 이 과정에서 확인한다.
옵티마이저
사용자의 요청으로 들어온 쿼리 문장을 저렴한 비용으로 가장 빠르게 처리할지를 결정하는 역할을 담당하며, MySQL의 두뇌 역할을 한다.
옵티마이저는 여러 가지 핵심 기능을 수행한다. 먼저 사용 가능한 인덱스를 확인하고 각 인덱스의 통계 정보를 바탕으로 어떤 인덱스를 사용할지 결정한다. 또한 테이블의 조인 순서를 결정하는데, 이는 쿼리 성능에 큰 영향을 미친다. WHERE 조건절을 최적화하여 불필요한 비교를 제거하고, GROUP BY나 ORDER BY를 효율적으로 처리하는 방법을 선택한다. 서브쿼리를 최적화하거나 조인으로 변환하는 등의 쿼리 재작성도 수행한다. 이러한 모든 결정은 비용 기반 최적화(Cost-based Optimization) 방식으로 이루어지며, 이후 별도의 블로그에서 더 자세히 다룰 예정이다.
실행 엔진
옵티마이저가 두뇌라면 실행 엔진과 핸들러는 손과 발에 비유할 수 있다. 간략하게 설명하면 아래와 같다.
옵티마이저가 GROUP BY를 처리하기 위해 임시 테이블을 사용하기로 했을 때:
- 실행 엔진이 핸들러에게 임시 테이블을 만들라고 요청
- 다시 실행 엔진은 WHERE 절에 일치하는 레코드를 읽어오라고 핸들러에게 요청
- 읽어온 레코드들을 1번에서 준비한 임시 테이블로 저장하라고 다시 핸들러에게 요청
- 데이터가 준비된 임시 테이블에서 필요한 방식으로 데이터를 읽어오라고 핸들러에게 다시 요청
- 최종적으로 실행 엔진은 결과를 사용자나 다른 모듈로 넘김
즉, 실행 엔진은 옵티마이저가 만들어진 계획대로 각 핸들러에게 요청해서 받은 결과를 또 다른 핸들러 요청의 입력으로 연결하는 역할을 한다.
핸들러(스토리지 엔진)
핸들러는 MySQL 엔진의 요청에 따라 데이터를 디스크에 저장하고, 디스크로부터 읽어오는 역할을 담당한다. 실제로 테이블의 데이터를 읽고 쓰는 모든 물리적 작업은 핸들러를 통해 수행되며, 각 스토리지 엔진(InnoDB, MyISAM 등)마다 다른 방식으로 구현되어 있다. MySQL 엔진은 핸들러 인터페이스를 통해 일관된 방식으로 다양한 스토리지 엔진과 통신한다.
복제
MySQL 서버에서 복제(Replication)는 매우 중요한 역할을 담당한다. 복제는 한 MySQL 서버(소스 서버)의 데이터를 다른 하나 이상의 MySQL 서버(레플리카 서버)로 동기화하는 기술이다. 주로 데이터 백업, 읽기 성능 향상을 위한 부하 분산, 그리고 고가용성(HA) 구성에 활용된다. 소스 서버는 모든 변경 사항을 바이너리 로그에 기록하고, 레플리카 서버는 이를 읽어와 자신의 데이터에 반영한다. 복제 관련 내용은 별도의 단원에서 더 자세히 다루게 된다.
쿼리 캐시
쿼리 캐시는 MySQL 5.7까지 제공되던 기능으로, 실행된 쿼리의 결과를 메모리에 캐싱하여 동일한 쿼리가 들어왔을 때 빠르게 결과를 반환하는 기능이었다. 하지만 MySQL 8.0부터는 완전히 제거되었다. 그 이유는 테이블의 데이터가 변경될 때마다 관련된 모든 쿼리 캐시를 무효화해야 했기 때문에 심각한 동시성 처리 성능 저하를 유발했기 때문이다. 특히 쓰기 작업이 많은 환경에서는 쿼리 캐시가 오히려 성능을 떨어뜨리는 요인이 되었다.
스레드 풀
스레드 풀은 MySQL 엔터프라이즈 에디션에서 제공하는 상용 기능으로, 커뮤니티 에디션에서는 기본적으로 제공되지 않는다. 하지만 Percona Server의 스레드 풀 플러그인을 설치하면 커뮤니티 에디션에서도 사용할 수 있다. 스레드 풀은 제한된 수의 스레드로 많은 수의 클라이언트 커넥션을 처리하는 기능이다. 일반적으로 MySQL은 커넥션마다 스레드를 할당하는데, 동시 접속자가 수천 명에 달하면 그만큼의 스레드가 생성되어 컨텍스트 스위칭 비용이 급증하고 CPU 자원이 낭비된다.
스레드 풀을 사용하면 제한된 수의 스레드(보통 CPU 코어 수만큼)가 작업 큐에서 쿼리를 가져와 처리한다. 이를 통해 컨텍스트 스위칭을 줄이고 CPU 자원을 효율적으로 사용할 수 있다. 특히 동시 접속자가 많거나, 짧은 쿼리가 대량으로 들어오는 OLTP 환경에서 유용하다. 이 기능이 유료인 이유는 구현의 복잡도와 엔터프라이즈 환경에서의 성능 최적화에 대한 가치 때문이다.
트랜잭션 지원 메타데이터
MySQL 8.0부터 메타데이터(테이블 구조 정보 등)가 트랜잭션을 지원하는 InnoDB 테이블에 저장되도록 변경되었다. 이전 버전에서는 메타데이터가 파일 기반(.FRM 파일)으로 관리되어 트랜잭션이 적용되지 않았다. 이로 인해 데이터와 메타데이터 간의 불일치가 발생할 수 있었고, 크래시 복구 시 문제가 생길 수 있었다. 트랜잭션 기반 메타데이터 관리로 전환되면서 원자성이 보장되고, 시스템의 일관성과 안정성이 크게 향상되었다.
'Database' 카테고리의 다른 글
| InnoDB 스토리지 엔진 아키텍처 (0) | 2025.10.09 |
|---|---|
| MySQL, PostgreSQL의 날짜와 시간 타입이 만든 혼란 (1) | 2025.04.23 |
| [ Database ] 정규화, 비정규화, 역정규화 (0) | 2024.01.08 |
| [데이터베이스] RDBMS, NoSQL (2) | 2023.12.16 |
| [데이터베이스] Index (2) | 2023.12.01 |