Foundry
성능 최적화
중급
핵심

슬로우 쿼리와 실행 계획 튜닝

대부분의 병목은 DB에 있다. 추측 대신 실행 계획

슬로우 쿼리와 실행 계획 튜닝

진단 순서

  1. 슬로우 쿼리 로그로 느린 쿼리와 호출 횟수를 수집
  2. EXPLAIN ANALYZE로 추정 행수와 실제 행수를 비교
  3. 풀 스캔, 불필요한 정렬, 비효율적 조인 방식 확인
  4. 인덱스 추가 또는 쿼리 재작성
  5. 같은 조건으로 전후를 다시 측정

자주 나오는 원인

증상원인조치
인덱스가 있는데 사용되지 않음컬럼 가공, 타입 불일치함수 적용 대신 범위 조건으로 변경
뒤 페이지로 갈수록 느려짐큰 OFFSET키셋(커서) 페이징
목록 크기만큼 쿼리 수 증가ORM 지연 로딩조인 또는 IN 배치 조회
추정 행수와 실제 행수 차이가 큼낡은 통계통계 갱신 후 재확인

실무 포인트

  • 한 번 아주 느린 쿼리보다, 빠르지만 매우 자주 불리는 쿼리의 총합이 더 큰 경우가 많다. 평균 시간 곱하기 호출 수로 정렬해서 본다
  • SELECT *는 커버링 인덱스를 무력화하고 네트워크와 메모리를 낭비한다
  • 인덱스 추가는 쓰기 비용과 저장 공간을 늘린다. 기존 인덱스로 커버되는지 먼저 확인한다
면접에서 이렇게 나옵니다

Q.느린 API를 받았을 때 DB 쪽 원인을 어떤 순서로 확인하나요?

순서확인알 수 있는 것
1그 API 가 실행한 쿼리 목록과 각 시간DB 가 원인인지, 몇 번째 쿼리인지
2쿼리 실행 횟수반복 호출(N+1)인지
3실행 계획인덱스를 타는지, 스캔 행수가 얼마인지
4잠금과 대기쿼리 자체는 빠른데 기다리는지
5커넥션 풀쿼리 전에 풀에서 기다리는지
6DB 자원다른 부하 때문에 전체가 느린지

1번과 2번을 먼저 하는 이유가 있습니다. 쿼리 하나가 느린 것과 빠른 쿼리를 100번 부르는 것은 총 시간이 같아도 대응이 완전히 다릅니다. 전자는 인덱스, 후자는 호출 구조입니다.

4번과 5번은 쿼리 자체를 봐서는 안 보입니다. 실행 계획이 완벽한데 느리면 대기를 봐야 합니다.

흔한 실수: 느린 쿼리 로그만 보고 판단하는 것. 임계값 미만의 쿼리가 수백 번 실행되는 경우는 그 로그에 안 남습니다. 요청 단위로 쿼리 수와 총 시간을 함께 봐야 합니다.

Q.EXPLAIN 결과에서 무엇을 먼저 보나요?

순서보는 것판단
1스캔한 행수와 반환한 행수의 비1,000행을 읽어 10행을 주면 걸러내는 비용이 크다
2접근 방식인덱스를 타는가, 전체를 훑는가
3어떤 인덱스를 썼는가의도한 인덱스인가
4정렬과 임시 테이블별도 정렬이나 임시 테이블이 생기는가
5조인 순서작은 쪽부터 시작하는가
6예상과 실제의 차이통계가 낡았는지 알 수 있다

1번이 가장 정보량이 많습니다. 읽은 행수가 결과 행수보다 훨씬 많으면 인덱스가 조건을 충분히 반영하지 못한 것입니다.

6번은 실제 실행 계획을 볼 때만 알 수 있습니다. 예상 1,000행인데 실제 100만 행이면 통계가 낡아 잘못된 계획을 고른 것이고, 통계를 갱신하면 해결되는 경우가 있습니다.

흔한 실수: 전체 스캔을 무조건 문제로 보는 것. 전체의 30% 이상을 읽는 쿼리라면 인덱스로 하나씩 찾아가는 것보다 순차 읽기가 빠릅니다. 행수와 함께 봐야 판단이 됩니다.

Q.페이지가 뒤로 갈수록 느려지는 문제를 어떻게 해결하나요?

건너뛰기 방식은 앞의 행을 실제로 읽고 버리기 때문입니다. 10만 번째 페이지를 보려면 10만 행을 읽고 버립니다.

방식동작뒤 페이지
OFFSET앞의 행을 모두 읽고 버린다선형으로 느려진다
커서 기반마지막으로 본 값 이후를 인덱스로 찾는다페이지 번호와 무관하게 일정하다

커서 방식은 이렇게 씁니다.

항목내용
조건WHERE (created_at, id) < (마지막값, 마지막id)
정렬그 컬럼 조합으로 인덱스를 만든다
동점 처리정렬 키가 같을 수 있으므로 고유 컬럼을 함께 넣는다

한계도 있습니다. 임의 페이지로 점프할 수 없고 이전 페이지로 가려면 별도 처리가 필요합니다. 그래서 무한 스크롤에는 잘 맞고, 페이지 번호를 보여주는 화면에는 그대로 쓰기 어렵습니다.

번호가 꼭 필요하면 앞쪽 몇 페이지만 번호로 주고 그 뒤는 검색 조건을 좁히도록 유도하는 방법을 씁니다.

흔한 실수: 인덱스만 추가하고 해결됐다고 보는 것. 인덱스가 있어도 건너뛴 행은 읽어야 하므로 뒤 페이지는 여전히 느립니다.

Q.인덱스를 추가하기 전에 무엇을 검토해야 하나요?

인덱스는 조회를 빠르게 하고 쓰기와 저장 공간을 대가로 받습니다. 그 거래가 맞는지 봅니다.

검토내용
기존 인덱스로 되는가복합 인덱스의 앞부분과 겹치면 새로 만들 필요가 없다
선택도값의 종류가 적으면(성별 등) 효과가 거의 없다
쓰기 빈도삽입과 수정이 잦은 테이블은 인덱스마다 비용이 든다
컬럼 순서복합 인덱스는 순서가 결과를 가른다
실제 쿼리 패턴자주 오는 조건인가. 한 번 쓰는 배치용은 아닌지
쿼리를 고칠 수 있는가함수를 씌워 인덱스를 못 타는 것이면 그쪽이 먼저

첫 번째가 가장 자주 놓칩니다. (a, b) 인덱스가 있으면 a 단독 조건도 그것을 씁니다. a 만 있는 인덱스를 또 만들면 쓰기 비용만 늡니다.

마지막도 중요합니다. WHERE DATE(created_at) = '2026-08-14' 는 인덱스를 못 타는데, 범위 조건으로 바꾸면 기존 인덱스로 해결됩니다.

흔한 실수: 느린 쿼리마다 인덱스를 하나씩 추가하는 것. 테이블에 인덱스가 열 개 넘게 쌓이면 쓰기가 눈에 띄게 느려지고, 옵티마이저가 잘못된 것을 고르기도 합니다.

먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.

더 깊이 공부하기

읽었으면 문제로 확인해보세요

성능 최적화 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.