한 줄 정의

MySQL 엔진은 SQL을 파싱하고 최적화하는 두뇌 역할이며, 스레드 기반으로 동작하고, 메모리를 글로벌/로컬 영역으로 나누어 관리합니다.

쉽게 말하면

MySQL 서버에 쿼리가 들어오면, 그 쿼리가 실제 디스크 데이터에 닿기까지 여러 단계를 거칩니다. 마치 공장의 생산 라인처럼 각 단계마다 전담 부서가 있고, 이 파이프라인 전체를 관리하는 것이 MySQL 엔진입니다.

스토리지 엔진은 이 파이프라인의 마지막 단계인 “데이터 읽기/쓰기”만 담당합니다. GROUP BY, ORDER BY 같은 복잡한 처리는 전부 MySQL 엔진의 쿼리 실행기에서 처리됩니다.

왜 중요한가?

쿼리 튜닝을 할 때 병목이 어디서 발생하는지 판단하려면 이 구조를 알아야 합니다.

  • 스토리지 엔진에서 느린 것인지(인덱스 미사용, 디스크 I/O) → 인덱스 튜닝
  • MySQL 엔진에서 느린 것인지(filesort, 임시 테이블) → 쿼리 구조 변경
  • 스레드/메모리 설정 문제인지 → 시스템 변수 튜닝

핵심 내용

전체 구조

flowchart TB
    subgraph Client["클라이언트"]
        JDBC["JDBC / ODBC / C API"]
    end

    subgraph MySQL_Engine["MySQL 엔진 (두뇌)"]
        ConnHandler["커넥션 핸들러"]
        Parser["쿼리 파서"]
        Preprocessor["전처리기"]
        Optimizer["옵티마이저"]
        Executor["실행 엔진 (쿼리 실행기)"]
    end

    subgraph Storage["스토리지 엔진 (손발)"]
        InnoDB["InnoDB"]
        MyISAM["MyISAM"]
        Others["MEMORY / CSV / ..."]
    end

    Client --> ConnHandler
    ConnHandler --> Parser
    Parser --> Preprocessor
    Preprocessor --> Optimizer
    Optimizer --> Executor
    Executor -->|핸들러 API| Storage
    Storage --> Disk[(디스크)]

MySQL 엔진은 하나 이지만, 스토리지 엔진은 테이블별로 다르게 지정할 수 있습니다.

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

MySQL 엔진과 스토리지 엔진 사이의 통신은 핸들러 API 를 통해 이루어집니다. SHOW GLOBAL STATUS LIKE 'Handler%' 명령으로 핸들러 호출 횟수를 확인할 수 있습니다.

스레딩 구조

MySQL은 프로세스 기반이 아닌 스레드 기반 으로 동작합니다.

포그라운드 스레드 (= 사용자 스레드)
  • 클라이언트 커넥션마다 하나씩 할당됩니다
  • 쿼리 처리를 담당하며, 데이터를 버퍼/캐시에서 가져옵니다
  • 커넥션 종료 시 스레드 캐시 로 반환됩니다 (thread_cache_size로 최대 개수 설정)
  • InnoDB 테이블: 데이터 버퍼까지만 처리하고, 디스크 쓰기는 백그라운드 스레드에 맡깁니다
  • MyISAM 테이블: 디스크 쓰기까지 포그라운드 스레드가 직접 처리합니다
백그라운드 스레드

InnoDB에서 특히 중요한 백그라운드 스레드들입니다:

스레드역할
로그 스레드리두 로그를 디스크에 기록
쓰기 스레드버퍼 풀의 더티 페이지를 디스크에 기록 (innodb_write_io_threads)
읽기 스레드데이터를 버퍼로 읽어옴 (innodb_read_io_threads)
인서트 버퍼 병합 스레드체인지 버퍼 내용을 실제 인덱스에 병합
데드락 모니터링 스레드잠금 교착 상태 감지

핵심 포인트: 쓰기 작업은 지연(버퍼링)할 수 있지만, 읽기 작업은 절대 지연할 수 없습니다. InnoDB는 이 원칙에 따라 쓰기를 백그라운드로 처리하여 사용자 쿼리의 응답 시간을 줄입니다.

스레드 풀

MySQL 커뮤니티 에디션은 커넥션당 하나의 포그라운드 스레드가 할당되는 전통적 모델을 사용합니다. 스레드 풀 은 엔터프라이즈 에디션이나 Percona Server에서 제공되며, 하나의 스레드가 여러 커넥션 요청을 전담하는 방식입니다.

Percona Server의 스레드 풀은 기본적으로 CPU 코어 수만큼 스레드 그룹을 생성하고(thread_pool_size), 각 그룹에 소속된 커넥션들을 담당합니다. 선순위 큐와 후순위 큐를 이용해 트랜잭션 내 쿼리를 우선 처리합니다.

메모리 할당 구조

MySQL의 메모리 영역은 공유 여부 에 따라 두 가지로 나뉩니다.

flowchart TB
    subgraph Global["글로벌 메모리 영역 (모든 스레드 공유)"]
        BP["InnoDB 버퍼 풀"]
        TC["테이블 캐시"]
        AHI["어댑티브 해시 인덱스"]
        RLB["리두 로그 버퍼"]
    end

    subgraph Local["로컬 메모리 영역 (스레드별 독립)"]
        SB["정렬 버퍼 (Sort Buffer)"]
        JB["조인 버퍼"]
        CB["커넥션 버퍼"]
        NB["네트워크 버퍼"]
        BLC["바이너리 로그 캐시"]
    end
글로벌 메모리 영역
  • MySQL 서버 시작 시 OS로부터 할당됩니다
  • 클라이언트 스레드 수와 무관하게 존재합니다
  • 모든 스레드가 공유합니다
로컬 메모리 영역 (= 세션 메모리 영역)
  • 각 클라이언트 스레드별로 독립 할당되며, 절대 공유되지 않습니다
  • 두 가지 할당 패턴이 있습니다:
    • 커넥션 유지 동안 상시 할당: 커넥션 버퍼, 결과 버퍼
    • 쿼리 실행 시에만 할당: 정렬 버퍼, 조인 버퍼
  • 글로벌 메모리는 주의해서 설정하면서 로컬 메모리(예: sort_buffer_size)는 간과하기 쉽지만, 커넥션 수 x 버퍼 크기 로 총 사용량이 결정되므로 최악의 경우 OOM이 발생할 수 있습니다

쿼리 실행 구조

쿼리가 실행되는 파이프라인을 단계별로 살펴봅니다.

1. 쿼리 파서

SQL 문장을 토큰 (최소 단위의 어휘/기호)으로 분리하여 파서 트리 를 생성합니다. 문법 오류는 이 단계에서 검출됩니다.

2. 전처리기

파서 트리를 기반으로 구조적 문제점을 확인합니다. 테이블/칼럼의 존재 여부, 접근 권한을 이 단계에서 검증합니다.

3. 옵티마이저

쿼리를 가장 적은 비용으로 가장 빠르게 처리할 방법 을 결정합니다. DBMS의 두뇌에 해당하며, 어떤 인덱스를 사용하고, 어떤 순서로 테이블을 조인할지 등을 결정합니다.

4. 실행 엔진 (쿼리 실행기)

옵티마이저가 만든 실행 계획대로 각 핸들러에게 요청을 보내는 관리자 역할입니다.

예를 들어, GROUP BY를 처리하기 위해 임시 테이블이 필요하면:

  1. 실행 엔진이 핸들러에게 “임시 테이블을 만들어라” 요청
  2. 핸들러에게 “WHERE 조건에 맞는 레코드를 읽어라” 요청
  3. 읽어온 레코드를 임시 테이블에 저장하도록 핸들러에게 요청
  4. 최종 결과를 클라이언트에게 반환
5. 핸들러 (스토리지 엔진)

실행 엔진의 요청에 따라 실제 디스크의 데이터를 읽고 씁니다.

플러그인 스토리지 엔진 모델

MySQL의 독특한 구조 중 하나입니다. 스토리지 엔진뿐 아니라 인증, 전문 검색 파서, 쿼리 재작성 등 다양한 기능이 플러그인 형태로 제공됩니다.

SHOW ENGINES로 지원 스토리지 엔진을, SHOW PLUGINS로 전체 플러그인 목록을 확인할 수 있습니다.

컴포넌트 (MySQL 8.0~)

플러그인의 단점을 보완하기 위해 MySQL 8.0에서 도입된 아키텍처입니다.

기준플러그인컴포넌트
상호 통신MySQL 서버와만 가능컴포넌트끼리도 가능
캡슐화서버 변수/함수 직접 호출 (안전하지 않음)인터페이스 기반 (캡슐화)
의존성상호 의존 관계 설정 불가의존 관계 관리 가능

예: 비밀번호 검증 기능이 5.7에서는 플러그인, 8.0에서는 컴포넌트로 제공됩니다.

INSTALL COMPONENT 'file://component_validate_password';

트랜잭션 지원 메타데이터 (MySQL 8.0~)

MySQL 5.7까지는 테이블 구조(메타데이터)를 .FRM 파일로 관리했습니다. 이 파일 기반 메타데이터는 트랜잭션을 지원하지 않아서, DDL 실행 중 서버가 비정상 종료되면 테이블과 실제 데이터 파일이 불일치하는 문제가 있었습니다.

MySQL 8.0부터는 메타데이터를 InnoDB 테이블 에 저장하여 트랜잭션 기반으로 관리합니다. 이로써 스키마 변경 중 비정상 종료에도 일관성이 보장됩니다.

정리

전통적 스레드 모델 vs 스레드 풀

기준전통적 모델스레드 풀
스레드:커넥션 관계1:1N:1
컨텍스트 스위칭커넥션 수에 비례하여 증가제한적 스레드로 최소화
적합한 환경커넥션 수가 적은 환경대량 커넥션, 짧은 쿼리
제공커뮤니티 에디션엔터프라이즈 / Percona

글로벌 메모리 vs 로컬 메모리

기준글로벌 메모리로컬 메모리
공유 범위모든 스레드해당 스레드만
할당 시점서버 시작 시커넥션/쿼리 시
위험 요소설정값 그대로 메모리 점유커넥션 수 × 버퍼 크기 = 총 사용량
대표 예시InnoDB 버퍼 풀Sort Buffer, Join Buffer

내 생각

  • MySQL 엔진과 스토리지 엔진의 역할 구분은 Handler_* 상태 변수를 통해 실무에서 바로 확인할 수 있습니다. Handler_read_rnd_next가 높다면 풀 테이블 스캔이 많다는 의미이므로 인덱스 점검이 필요합니다.

  • 로컬 메모리(특히 sort_buffer_size, join_buffer_size)를 크게 설정하면 커넥션이 몰릴 때 메모리 부족이 발생할 수 있습니다. 기본값을 유지하면서 특정 세션에서만 SET SESSION으로 조정하는 것이 안전합니다.

  • MySQL 8.0에서 메타데이터가 InnoDB 기반으로 전환된 것은, DDL을 자주 수행하는 마이그레이션 상황에서 안정성을 크게 향상시킵니다. 5.7 이하 버전에서 겪었던 “FRM 파일 불일치” 문제가 사라졌습니다.

관련 개념

출처

  • Real MySQL 8.0 (1권), 4.1 MySQL 엔진 아키텍처