MySQL 성능 최적화, 이것만 알면 쿼리 속도가 확 빨라져요!
혹시 이런 경험 있으신가요? 데이터가 몇만 건일 땐 멀쩡하던 서비스가, 데이터가 쌓이니 페이지 하나 여는 데 몇 초씩 걸리는 상황요. 😥 오늘은 MySQL 성능 최적화의 핵심을 친구에게 설명하듯 쉽게 풀어드릴게요. 인덱스, 쿼리 튜닝, 서버 설정까지 순서대로 짚어드릴 테니, 다 읽고 나면 “아, 우리 DB 어디부터 손봐야겠구나” 감이 딱 잡히실 거예요. 👍

먼저 범인을 찾자: EXPLAIN으로 쿼리 들여다보기 🔍
성능이 느릴 때 무작정 인덱스부터 추가하면 안 돼요. 먼저 어디가 느린지 진단하는 게 순서죠. 이때 쓰는 게 바로 EXPLAIN 명령어예요. 쿼리 앞에 EXPLAIN만 붙이면, MySQL이 이 쿼리를 어떻게 실행할 계획인지 보여줘요.
여기서 꼭 봐야 할 게 type 컬럼이에요. 이 값이 ALL로 나오면 테이블 전체를 처음부터 끝까지 훑는 ‘풀 테이블 스캔’이라는 뜻이라, 데이터가 많을수록 치명적이에요. ⚠️ 반대로 const, ref, range 같은 값이면 인덱스를 잘 타고 있다는 신호예요.
MySQL 8.0부터는 EXPLAIN ANALYZE도 쓸 수 있어요. 예상값이 아니라 실제로 걸린 시간과 처리한 행 수를 보여줘서, 진짜 병목이 어딘지 훨씬 정확하게 알 수 있답니다. 😊
인덱스: 가장 강력한 무기, 하지만 만능은 아니에요 💡
인덱스는 책 뒤에 있는 ‘찾아보기(색인)’와 똑같아요. 원하는 단어를 처음부터 다 넘겨보지 않고 색인에서 바로 찾는 것처럼, 인덱스가 있으면 원하는 데이터를 훨씬 빨리 찾아요.
어떤 컬럼에 인덱스를 걸어야 할까?
- WHERE 조건에 자주 쓰는 컬럼: 필터링에 사용되는 컬럼이 1순위예요.
- JOIN이나 ORDER BY에 쓰이는 컬럼: 정렬·연결에도 인덱스가 큰 도움이 돼요.
- 카디널리티(고유값 수)가 높은 컬럼: 값이 다양할수록 효과가 커요. 반대로 ‘성별’처럼 값이 몇 개 안 되는 컬럼은 인덱스 효과가 약합니다.
복합 인덱스와 커버링 인덱스
두 개 이상 컬럼을 묶는 복합 인덱스는 여러 조건을 함께 쓰는 쿼리에 강력해요. 다만 컬럼 순서가 중요해서, 자주 쓰고 선택도가 높은 컬럼을 앞에 두는 게 유리합니다.
한 걸음 더 나아가, 쿼리에 필요한 컬럼이 전부 인덱스 안에 들어 있으면 테이블을 따로 읽지 않고 인덱스만 보고 끝낼 수 있어요. 이걸 커버링 인덱스라고 하는데, 디스크 접근을 크게 줄여줘서 체감 속도가 확 달라진답니다. ✅
⚠️ 인덱스도 과하면 독이 돼요
인덱스가 많으면 조회는 빨라지지만, 데이터를 넣고 고칠 때(INSERT·UPDATE·DELETE)마다 인덱스도 같이 갱신해야 해서 쓰기 성능이 떨어져요. 실제 벤치마크에서도 중복 인덱스가 많은 테이블은 쓰기 부하가 몇 배씩 늘어나기도 합니다. 그래서 안 쓰는 인덱스는 정리하는 게 좋아요. MySQL의 sys.schema_unused_indexes를 조회하면 안 쓰이는 인덱스를 찾을 수 있어요.
쿼리 자체를 다듬기: 작은 습관이 큰 차이를 만들어요 ✍️
같은 결과를 내는 쿼리라도 어떻게 쓰느냐에 따라 속도가 크게 달라져요. 몇 가지 쉬운 팁만 챙겨보세요.
- SELECT *는 피하기: 필요한 컬럼만 콕 집어 가져오세요. 커버링 인덱스 효과도 살릴 수 있어요.
- OR 대신 UNION 고려하기: OR 조건은 인덱스를 제대로 못 타는 경우가 많아요. 조건별로 나눠 UNION으로 합치면 각 조건이 인덱스를 타서 빨라질 수 있어요.
- 인덱스 컬럼을 함수로 감싸지 않기: WHERE 절에서 컬럼에 함수나 연산을 걸면 인덱스를 못 씁니다.
- 페이지네이션 주의: OFFSET 값이 커지면 느려져요. 마지막으로 본 값 기준으로 다음 데이터를 가져오는 방식이 훨씬 효율적이에요.
서버 설정 한 줄이 판도를 바꾼다: InnoDB 버퍼 풀 ⚙️
쿼리와 인덱스를 아무리 다듬어도, 서버 설정이 기본값 그대로면 아쉬워요. 그중 가장 중요한 게 바로 innodb_buffer_pool_size입니다. 이건 자주 쓰는 데이터와 인덱스를 메모리에 캐싱해 두는 공간이에요. 여기서 데이터를 찾으면(캐시 히트) 디스크보다 훨씬 빠르거든요.
그런데 이 값의 기본이 겨우 128MB예요. 64GB 서버에 이 기본값이 그대로 남아 있는 경우가 정말 흔하답니다. 😅 전용 DB 서버라면 전체 RAM의 약 70~80%를 할당하는 게 일반적인 권장 출발점이에요. 다만 다른 프로그램이 같이 도는 서버라면 OS와 다른 프로세스 몫으로 여유를 남겨야 하니, 상황에 따라 조절이 필요해요.
잘 맞췄는지 확인하려면 버퍼 풀 히트율을 보세요. 99% 이상이면 아주 건강하고, 95% 아래로 떨어지면 버퍼 풀이 작다는 신호예요. 참고로 MySQL 8.0부터는 innodb_dedicated_server 옵션을 켜면 서버 사양에 맞춰 자동으로 잡아주기도 해요.
느린 쿼리를 자동으로 잡아내기: 슬로우 쿼리 로그 📋
어떤 쿼리가 느린지 일일이 찾기 힘들죠? 그럴 땐 슬로우 쿼리 로그(slow query log)를 켜보세요. long_query_time에 기준 시간을 정해두면, 그보다 오래 걸린 쿼리를 알아서 기록해 줘요. 로그를 보면 “아, 이 쿼리가 서비스를 잡아먹고 있었구나” 하고 범인을 딱 잡을 수 있답니다. 👍
마무리: 최적화는 ‘한 번’이 아니라 ‘꾸준히’ 😊
오늘 내용을 짧게 정리해 볼게요. MySQL 성능 최적화는 ① EXPLAIN으로 진단하고 ② 인덱스를 똑똑하게 걸고 ③ 쿼리를 다듬고 ④ 버퍼 풀 같은 서버 설정을 맞추고 ⑤ 슬로우 쿼리 로그로 계속 모니터링하는 흐름이에요. 이 다섯 가지만 챙겨도 체감 속도가 확 달라질 거예요.
다만 정답이 하나로 정해진 건 아니에요. 읽기가 많은 서비스와 쓰기가 많은 서비스는 최적의 설정이 다르고, 인덱스도 워크로드에 따라 답이 갈려요. 그러니 오늘 배운 것 중 가장 간단한 것부터 하나씩 적용해 보세요. 여러분의 느려진 DB, 지금 EXPLAIN 한 줄부터 시작해 보는 건 어때요? 💪
혹시 특정 쿼리가 유독 느려서 고민이라면, 실행 계획을 함께 뜯어보면서 어디를 손봐야 할지 이야기 나눠봐도 좋아요. 댓글로 여러분의 고민을 남겨주세요! 😊