MySQL 아키텍처

1. MySQL 접속 클라이언트
MySQL에 접속할 수 있게 해주는 API로, MySQL Connector와 Shell이 있다.
2. MySQL 엔진
클라이언트 접속과 SQL 요청을 처리하는 두뇌 역할이다.
쿼리 파서, 전처리기, 옵티마이저, 실행 엔진 등으로 구성되어 있다.
옵티마이저는 요청된 SQL문을 최적화해서 실행시키기 위해 실행 계획을 짜는 중요한 역할이다.
3. MySQL 스토리지 엔진
데이터를 실제로 디스크에 저장하거나, 디스크에 저장된 데이터를 읽어오는 역할을 한다.
옵티마이저가 작성한 실행 계획에 따라 스토리지 엔진을 적절히 호출해서 쿼리를 실행한다.
MySQL 엔진이 스토리지를 호출할 때 사용하는 API를 핸들러 API라고 한다.
핸들러 API를 직접 구현해서 나만의 스토리지 엔진을 추가할 수도 있다.
4. 운영체제, 하드웨어
실제 테이블의 데이터와 로그 데이터를 파일로 저장하는 운영체제 파일 시스템과 하드웨어 부분이다.
쿼리 실행 과정

쿼리 캐시
- 사용자가 SQL 요청을 MySQL로 보내면 가장 먼저 쿼리 캐시를 만난다.
- 쿼리 캐시는 쿼리 요청 결과를 캐싱하는 모듈이다.
- 동일한 SQL 요청에 대한 결과를 빠르게 받을 수 있다.
- 쿼리 캐시는 가지고 있는 데이터의 테이블에 변경이 발생한다면 쓸모없어진 캐싱 데이터를 삭제해야 한다.
캐싱 데이터가 삭제될 때마다 쿼리 캐시에 접근하는 쓰레드에 Lock이 걸리는데, 심각한 동시 처리 성능 저하를 유발한다.
따라서 MySQL 8.0부터 쿼리 캐시가 완전히 삭제되었다.
쿼리 파서
- SQL 문장을 의미있는 단위의 토큰으로 쪼개서 트리(Parse Tree)로 만든다.
- 이 과정에서 SQL 문법 오류를 체크한다.
전처리기
- Parse Tree의 토큰을 하나씩 검사하면서 토큰이 유효한지, 토큰의 테이블명이나 컬럼이 실제 존재하는 값인지 체크한다.
옵티마이저
- SQL 실행을 최적화해서 쿼리 실행 계획을 만든다.
- 규칙 기반 최적화 : 옵티마이저에 내장된 우선순위에 따라 실행 계획 수립 (원칙적으로 어떤 방식이 더 빠른가에 초점)
- 비용 기반 최적화 : SQL을 처리하는 다양한 방법을 마련해두고, 각 방법의 비용과 테이블 통계 정보를 통해 실행 계획 수립 (레코드 수, 데이터의 분포 상태 등을 반영해서 최적의 방법을 고르는 방식)
- 규칙 기반은 빠르다는 장점, 비용 기반은 효율적이라는 장점을 가짐
- 규칙 기반은 초기 MySQL에서 사용한 방식, 최근에는 대부분 비용 기반 방식 사용
쿼리 실행 엔진
- 옵티마이저가 만든 실행 계획대로 스토리지 엔진을 호출해서 쿼리를 수행한다.
스토리지 엔진
- 쿼리 실행 엔진이 요청하는대로 데이터를 디스크로 저장하고 읽는다.
- 플러그인 형태로 제공되기 때문에 사용자는 원하는 스토리지 엔진을 선택해서 사용할 수 있다.
- 다양한 스토리지 엔진과의 상호작용이 이루어지도록 중간 다리 역할을 해주는 게 핸들러 API이다.
- ex. InnoDB, MyISAM
MySQL은 스토리지 엔진 말고도 검색어 파서, 사용자 인증 모듈 등도 플러그인 형태로 제공한다.
플러그인끼리 통신할 수 없고, 플러그인이 MySQL 서버의 변수나 함수를 직접 호출하는 것 때문에 캡슐화 위반한다는 단점이 있다.
따라서 MySQL 8.0부터는 이러한 플러그인 아키텍처의 단점을 보완한 컴포넌트 아키텍처를 제공한다.
컴포넌트 아키텍처가 도입되면서 MySQL 서버 내부를 모듈로 구성하고, 이 컴포넌트(모듈)끼리 인터페이스를 통해 통신한다.
=> 캡슐화, 의존성 관리, 통신이 체계화 가능해짐
기본 파서 (영어 기준)
'hello world' → ['hello', 'world']
사용자 정의 파서 (한국어 예시)
'나무위키는 누구나 편집할 수 있습니다' → ['나무위키', '편집', '누구나', '수', '있습니다'] 등으로 쪼개도록 구현 가능
=> 검색 품질을 높이기 위해 파서를 커스터마이징 할 수 있는게 검색어 파서
사용자 인증 모듈은 MySQL에 로그인하려는 사용자의 인증 방식을 결정하는 기능
자체 로직을 통해 사용자 인증을 수행하고 싶을 때 인증 플러그인 사용
mysql_native_password
→ 전통적인 MySQL 비밀번호 방식 (암호화된 패스워드 비교)
caching_sha2_password
→ MySQL 8.0의 기본 인증. 더 보안 강화됨.
커스텀 플러그인
→ 예를 들어 Google OAuth 또는 자체 개발한 API를 이용한 로그인 인증도 가능
InnoDB 스토리지 엔진
PK에 의한 클러스터링
- 레코드를 PK순으로 정렬해서 저장하기 때문에 범위 검색이 매우 빠르다.
- 하지만 클러스터링을 쓰기 때문에 쓰기 성능이 저하된다.
- PK를 지정하지 않으면 내부적으로 PK 인덱스 자동 생성한다.
- 사용자는 자동 생성된 PK에 접근할 수 없어서 데이터를 조회할 때 성능을 최적화하기 어렵다.
- 따라서 PK는 직접 지정하는 것을 권장한다.
트랜잭션 지원
- MVCC(Multi Version Concurrency Control) 지원
- 다양한 버전을 동시에 관리하는 것을 의미한다.
- 버퍼풀 : 변경된 데이터를 디스크에 저장하기 전까지 잠깐 버퍼링 하는 공간
- 언두 로그 : 변경되기 전의 데이터를 백업해두는 공간 -> 롤백 지원!


다른 트랜잭션이 유재석의 취미를 검색한다면?
DB에 설정된 트랜잭션 격리 수준에 따라 다르다.
격리 수준이란 여러 트랜잭션이 동시에 실행될 때, 서로의 작업이 얼마나 영향을 미치지 않도록 격리할 것인가를 결정하는 기준이다.
READ_UNCOMMITED : 아직 커밋되지 않은 최신 변경 상태인 '코딩' 반환
READ_COMMITED, REPEATABLE_READ, SERIALIZABLE : 커밋된 상태인 '독서' 반환
=> 레코드에 잠금을 걸지 않아도 레코드의 격리 수준에 맞게 읽기를 할 수 있는 것이 바로 MVCC 기술이다.
레코드 단위 잠금
DB에서 데이터를 변경할 때 동시성 문제를 고려해서 레코드에 대한 접근을 막는 것이다.
InnoDB는 인덱스 기반의 레코드 단위 잠금을 수행한다.
MVCC는 읽기 전용 트랜잭션의 동시성을 높이기 위한 기술이고, 쓰기(수정, 삭제)에는 두 트랜잭션이 동시에 같은 레코드를 수정하면 안되기 때문에 인덱스 기반 레코드 잠금이 필수이다.
인덱스 기반의 레코드 잠금을 사용하지 않으면 테이블 전체를 스캔해야 해서 불필요하게 많은 레코드에 잠금이 걸리고, 성능 저하와 데드락 위험이 증가한다.


인덱스를 사용하면 해당 레코드만 잠금을 하기 때문에 동시성이 향상된다.
InnoDB 버퍼풀
- 디스크의 데이터 파일이나 인덱스 정보를 메모리에 캐싱해두는 공간이다.
- 버퍼풀은 쓰기 지연 버퍼로도 사용된다.
- SQL 요청 결과를 일정한 크기의 페이지 단위로 캐싱한다.
- 페이지 교체 알고리즘으로 LRU 알고리즘을 사용하고 있다.
- 더티 페이지를 모았다가 이벤트를 발생시켜 디스크에 반영하는데, 이것은 랜덤 IO를 줄이기 위함이다.
- 더티 페이지는 DDL 명령어로 변경된 페이지다.
- 랜덤 IO는 디스크 여기저기 흩어진 데이터를 읽고 쓰는 작업이다.
어댑티브 해시 인덱스
- 인덱스 키와 페이지의 주소값 쌍으로 구성된 인덱스이다.
- 사용자가 자주 요청하는 데이터에 대해서 InnoDB가 자동으로 만들어주는 인덱스이다.
- 어댑티브 해시 인덱스를 통해 빠르게 데이터에 접근할 수 있어서 쿼리 역시 더 빠르게 처리가 가능하다.
MyISAM 스토리지 엔진
- 클러스터링, 트랜잭션, 외래키 모두 지원하지 않는다.
- 테이블 단위로 잠금을 걸기 때문에 동시 처리에 불리하다.
- InnoDB의 버퍼풀과 비슷한 기능을 하는 키 캐시가 존재한다.
- 버퍼풀과 다르게 키와 레코드 위치를 저장
기존에는 MyISAM을 스토리지 엔진으로 사용했지만 MySQL 5.5버전부터 InnoDB 스토리지 엔진이 기본 엔진으로 채택되었다.
시스템 테이블(메타데이터를 저장하는 내부 테이블)은 여전히 MyISAM을 사용했지만 MySQL 8.0부터 모든 테이블이 InnoDB 스토리지를 사용하게 되었다.

'개발 지식' 카테고리의 다른 글
EOF(End of File) 개념과 사용 예시 (0) | 2025.03.27 |
---|---|
프로세스와 스레드의 동작 원리 (0) | 2025.03.23 |
웹서버 애플리케이션 관점에서의 Thread Pool (0) | 2025.03.21 |
NullPointerException(NPE) 런타임 에러 (1) | 2025.03.11 |
JVM 메모리 구조 (0) | 2025.02.25 |