MySQL 완벽 가이드 2026 | InnoDB·MVCC·옵티마이저부터 복제까지
이 글의 핵심
인덱싱·EXPLAIN 실전에 더해, InnoDB가 페이지와 리두로 어떻게 동작하는지, MVCC가 스냅샷 읽기를 구현하는 방식, 옵티마이저가 실행 계획을 고르는 근거, 복제가 바이너리 로그로 변경을 전달하는 구조, 그리고 프로덕션에서 풀링·지연·백업을 다루는 패턴까지 한 번에 정리합니다.
MySQL이 느리다고? 솔직히 엔진 자체보다는 스키마·인덱스·락·복제 지연 쪽이 범인인 경우가 훨씬 많아. 이 글은 교과서 목차로 정리하려다 말고, 밀어붙이는 기준 하나만 먼저 박을게. InnoDB 말고 다른 저장 엔진 쓰려면 이유가 있어야 한다. 취미로 Memory나 MyISAM 골라 쓰는 건 2020년대 OLTP에선 말이 안 돼. 트랜잭션이랑 행 잠금, FK, 크래시 복구까지 기본으로 갖춘 쪽이 InnoDB고, “그냥 예전에 그렇게 썼어요”는 운영 난이도만 올린다.
인덱스 잘못 걸어서 꽤 망한 적이 있어. 케이스는 흔한 그거야. 주문 조회에 user_id랑 created_at을 같이 쓰는데, 누가 “일단 created_at만 인덱스 있으면 되지” 하고 걸어 둔 거지. WHERE user_id = ? AND created_at BETWEEN ? AND ? 패턴이 하루에 몇십만 번 돌아가는 서비스였는데, 옵티마이저는 앞에 동등(=) 조건이 먹는 복합 인덱스를 제일 잘 쓰거든. 컬럼 순서 뒤집혀 있으면 range는 나와도 스캔 범위가 늪이 돼. 그날 p99가 10초 가까이 튀어서, 슬로 로그 뜯다가 rows랑 Extra 보면서 “이게 왜 Using filesort/Using temporary까지 같이 뜨지?” 하다가, 인덱스 정비하고 나서 지연이 100배 가까이 줄어든 적도 있고. 그래서 인덱스는 “있으면 됨”이 아니라 쿼리 모양·정렬·커버링이랑 같이 봐야 한다는 걸 몸으로 배움. 실수 포인트: 카디널리티 낮은 앞에 두기, SELECT * 습관, 함수 감싼 컬럼, 그리고 “검색만 빨리 보이는” 쪽으로만 쏠리다 쓰기 폭발 잊기.
InnoDB는 데이터랑 1차 인덱스(클러스터)가 같이 붙어 있다고 보면 돼. PK 설계를 대충 잡으면(무작위 UUID를 그대로 박아서 페이지 단편화·쓰기 핫스팟 나는 류) 보조 인덱스 뒤집다가 I/O에 발목 잡힌다. 버퍼 풀(innodb_buffer_pool_size)이 워킹 세트를 못 품으면 랜덤 읽기 지옥. 리두/언두는 “왜 히스토리가 길어질수록 읽기가 무거워지지?”에 대한 답이고, 트랜잭션이 길어지면 purge가 밀리면서 버전 쌓임이 커짐. 그냥 “쿼리 느려요”가 아니라 동시에 돌고 있는 락/버전/복제가 얽힌 문제라서, 격리 수준·SELECT FOR UPDATE 범위 이런 것도 눈에 넣고 봐야 함. RR 기본이면 gap·next-key 뜯어보는 맛이 있다.
-- 인덱스 컬럼에 함수: 자주 죽는 패턴
SELECT * FROM users WHERE YEAR(created_at) = 2026;
-- 이런 쪽이 현실적
SELECT * FROM users
WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01';
EXPLAIN SELECT * FROM users WHERE email = '[email protected]';
-- type / key / rows / Extra 같이 봐야 함, 추정 row가 허튼짓 중이면 그게 첫 수술 대상
옵티마이저는 통계랑(8.0 히스토그램 포함) 비용으로 고르는데, 대량 로드 뒤에 ANALYZE TABLE 안 해놓고 “왜 갑자기 풀스캔이지?” 하는 경우도 흔해. 힌트는 재현·측정이 끝난 뒤, 그리고 “임시 땜질”로만 쓰는 걸 추천. 버전 올릴 때 고정돼 있으면 유지보수가 괴롭다.
복제 이야기는 짧게. ROW 포맷이랑 GTID 쪽이 실무에서 덜 꼬이고, 비동기 복제면 뒤처짐 = 정상이라고 머릿속에 박아둬. 쓰고 바로 읽는 플로우는 프라이머리로 보내거나, “낡은 읽기 허용”을 제품 말로 써야 한다. 세미싱크는 손실 줄이는 대신 지연이 늘 수 있고, GR/Cluster는 HA 목표엔 좋은데 운영 난이도가 꽤 올라감. 이것도 “팀이 감당 가능한가?”가 먼저.
프로덕션은 지루하게 말하면 풀, 백업, 슬로 로그, 복제 랙 네 갈래가 기본. 요청마다 커넥션 새로 파면 금방 터지니까 앱 풀 + (환경에 따라) ProxySQL·RDS Proxy 같은 쪽. 백업은 mysqldump이든 XtraBackup이든, 복원 연습 없으면 “백업 있대요”가 아니라 “디스크에 파일 있대요”에 가깝다. 8.0이면 쿼리 캐시 얘기는 그냥 잊으면 됨(없어짐).
[mysqld]
innodb_buffer_pool_size = 8G
max_connections = 500
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 0.5
정리하자면, 나는 InnoDB 아니면 변명 먼저 보고, 인덱스는 쿼리·정렬·쓰기 세 줄 다 보고, 복제는 지연을 제품에 쓰는지부터 정한다. “완벽 가이드”라고 써놓고 목차 딱딱 맞출 생각은 없고, 느릴 땐 EXPLAIN·슬로 로그·버퍼 히트·락·복제 lag를 한 번에 의심해 보면 반은 간다. 더 파고들 거면 database-mysql-explain-query-optimization 글이랑 같이 보면 됨.