MySQL covering index 가이드: 테이블 row lookup을 줄이는 가장 실용적인 방법
DB
마지막 업데이트

MySQL covering index 가이드: 테이블 row lookup을 줄이는 가장 실용적인 방법


MySQL 인덱스를 공부하다 보면 covering index 라는 표현을 자주 보게 됩니다. 처음에는 “인덱스가 있으면 다 비슷한 거 아닌가?” 싶지만, 실제로는 이 개념을 이해하고 나면 왜 어떤 조회는 유난히 가볍고 어떤 조회는 인덱스를 타도 여전히 묵직한지 감이 훨씬 잘 잡힙니다.

핵심은 단순합니다. 일반 인덱스는 보통 찾는 경로를 빠르게 만들어 주고, covering index는 그 경로 안에서 필요한 값까지 바로 반환할 수 있게 해줍니다. 즉 “찾고 나서 테이블 row를 다시 읽는 단계”를 줄일 수 있다는 뜻입니다.

이 글에서는 아래를 중심으로 정리합니다.

  • covering index가 정확히 무엇인지
  • 일반 인덱스와 무엇이 다른지
  • InnoDB에서 왜 추가 row lookup 비용이 생기는지
  • 어떤 쿼리에서 효과가 큰지
  • 왜 모든 쿼리를 covering index로 만들 수는 없는지
  • EXPLAIN 에서 어떤 신호를 보면 좋은지

한 줄 요약은 이렇습니다. covering index는 인덱스를 “찾는 도구”에서 “찾고 필요한 값까지 바로 돌려주는 읽기 경로”로 바꾸는 설계입니다.

커버링 인덱스 개념


Quick answer

실무에서는 아래 기준으로 보면 됩니다.

  1. 자주 실행되는 읽기 쿼리인지 먼저 봅니다.
  2. SELECT 컬럼이 적고 안정적인지 확인합니다.
  3. WHERE, ORDER BY, SELECT 에 필요한 컬럼이 인덱스 안에서 대부분 해결되는지 봅니다.
  4. 일반 인덱스를 타고도 테이블 row를 다시 읽는 비용이 큰 패턴인지 확인합니다.
  5. EXPLAIN 에서 Using index 같은 신호를 확인합니다.
  6. 다만 인덱스 폭이 너무 넓어져 write 비용과 저장 공간이 커지지 않는지 같이 봅니다.

즉 covering index의 핵심 질문은 “인덱스를 쓰느냐”가 아니라 인덱스만 읽고 끝낼 수 있느냐입니다.


1. covering index란 무엇인가

covering index는 쿼리에 필요한 컬럼이 인덱스 안에 모두 들어 있어서, MySQL이 원본 row를 다시 읽지 않고도 결과를 만들 수 있는 경우를 말합니다.

예를 들어 이런 쿼리가 있다고 합시다.

SELECT id, title, created_at
FROM posts
WHERE author_id = 42
ORDER BY created_at DESC
LIMIT 20;

만약 인덱스가:

(author_id, created_at, id, title)

처럼 쿼리에 필요한 핵심 컬럼을 모두 포함하고 있다면, MySQL은 인덱스만 읽고도 이 조회를 상당 부분 해결할 수 있습니다.

중요한 건 covering index가 “특별한 종류의 인덱스”라기보다, 어떤 인덱스가 특정 쿼리를 충분히 커버하는 상태라는 점입니다.

즉:

  • 인덱스 자체가 covering이라는 고정 속성을 갖는 게 아니라
  • 어떤 쿼리 기준으로는 covering일 수 있고, 다른 쿼리 기준으로는 아닐 수 있습니다

2. 일반 인덱스와 무엇이 다른가

일반 인덱스도 물론 성능에 도움이 됩니다. 하지만 많은 경우 일반 인덱스는 “후보 row를 빨리 찾는 역할”까지만 합니다.

예를 들어 WHERE author_id = 42 를 빠르게 좁혀도, 쿼리가 실제로 필요로 하는 title, created_at, summary 같은 값이 인덱스 안에 없으면 MySQL은 결국 원본 row를 다시 읽어야 합니다.

그래서 차이를 단순하게 표현하면 이렇습니다.

  • 일반 인덱스: 어디를 읽을지 빨리 찾는다
  • covering index: 어디를 읽을지 찾고, 필요한 값도 인덱스에서 바로 꺼낸다

즉 covering index의 이점은 “검색 성공” 이후에 생기는 추가 row lookup 비용을 줄이는 데 있습니다.


3. InnoDB에서는 왜 covering index가 특히 중요하게 느껴질까

이 개념이 특히 와닿는 이유 중 하나는 InnoDB에서 secondary index를 탈 때, 필요한 컬럼이 인덱스 안에 없으면 보통 한 번 더 원본 row 쪽으로 가야 하기 때문입니다.

실무 감각으로 보면 흐름은 이렇습니다.

  1. secondary index에서 조건에 맞는 엔트리를 찾습니다.
  2. 그런데 쿼리에 필요한 컬럼이 거기에 다 없으면
  3. 실제 row를 다시 읽어야 합니다.

이 추가 lookup이 row 수가 많아질수록 비용으로 쌓입니다.

반대로 covering index가 되면:

  1. 인덱스에서 조건을 찾고
  2. 필요한 결과 컬럼도 거기서 바로 읽고
  3. base row 접근을 줄이거나 생략할 수 있습니다

그래서 covering index는 단순히 “조금 더 좋은 인덱스”가 아니라, 읽기 경로의 단계 수 자체를 줄여주는 설계로 느껴질 때가 많습니다.


4. 언제 특히 효과가 큰가

covering index는 모든 쿼리에서 똑같이 중요하지 않습니다. 특히 효과가 큰 경우는 보통 아래와 같습니다.

결과 컬럼 수가 적은 반복 조회

목록 페이지, API 리스트 응답, 최근 N건 조회처럼 적은 컬럼을 반복적으로 읽는 쿼리에서 효과가 큽니다.

예를 들어:

  • 게시글 목록
  • 최근 주문 20건
  • 특정 사용자 최근 활동

같은 패턴입니다.

필터와 정렬이 안정적인 쿼리

WHEREORDER BY 조합이 자주 같다면, 인덱스 하나로 필터와 정렬, 결과 컬럼까지 함께 설계할 수 있습니다.

읽기 빈도가 높고 row lookup이 누적 비용이 되는 경우

한 번 실행해서 1ms 줄어드는 게 별것 아닐 수 있지만, 초당 수백 번 도는 조회라면 누적 효과가 커집니다.

즉 covering index는 “아주 무거운 한 방 쿼리”보다 작고 빈번한 읽기 경로를 더 가볍게 만드는 데 특히 강한 경우가 많습니다.


5. 어떤 쿼리에서는 왜 covering index가 잘 안 맞을까

covering index가 좋아 보여도, 모든 쿼리를 그렇게 만들 수는 없습니다.

SELECT *

컬럼을 너무 많이 읽는 쿼리는 현실적으로 covering index에 잘 맞지 않습니다. 필요한 컬럼을 전부 인덱스에 넣으려 하면 인덱스가 너무 커지고 관리 비용이 커집니다.

큰 텍스트나 JSON, 본문 데이터를 읽는 쿼리

상세 페이지처럼 본문 전체를 읽는 쿼리는 covering index보다 그냥 적절한 필터 인덱스로 후보를 빨리 찾는 쪽이 현실적일 때가 많습니다.

쿼리 패턴이 자주 바뀌는 경우

어제는 title, 오늘은 summary, 내일은 thumbnail_url 까지 다 가져오는 식이면 covering index의 이점이 희석됩니다.

즉 covering index는 읽는 컬럼이 좁고 안정적일수록 잘 맞습니다.


6. covering index 설계는 SELECT 컬럼 습관과도 연결된다

실무에서 covering index가 잘 안 나오는 가장 흔한 이유 중 하나는 SELECT * 습관입니다.

필요한 컬럼이 3개뿐인데도 전체 row를 읽도록 습관적으로 짜면:

  • 인덱스 설계 자유도가 줄고
  • 불필요한 I/O가 늘고
  • covering index로 갈 기회도 사라집니다

반대로 목록 쿼리에서 필요한 컬럼만 명확히 고르면:

SELECT id, title, created_at
FROM posts
WHERE author_id = 42
ORDER BY created_at DESC
LIMIT 20;

처럼 설계가 훨씬 쉬워집니다.

즉 covering index는 인덱스 기술만의 문제가 아니라, 쿼리에서 정말 필요한 컬럼만 읽는 습관과도 깊게 연결됩니다.


7. covering index라고 해서 컬럼을 무작정 많이 넣으면 안 된다

여기서 흔히 생기는 반대편 함정이 있습니다. “그럼 필요한 컬럼을 다 인덱스에 넣자”로 가는 것입니다.

하지만 인덱스가 커지면:

  • 저장 공간이 늘고
  • 메모리 효율이 나빠지고
  • 쓰기 비용이 커지고
  • 비슷한 인덱스가 우후죽순 생길 수 있습니다

즉 covering index는 공짜가 아닙니다.

그래서 항상 아래 균형을 같이 봐야 합니다.

  • 얼마나 자주 읽히는 쿼리인가
  • row lookup을 줄이는 이득이 큰가
  • 결과 컬럼이 충분히 작고 안정적인가
  • 쓰기 비용 증가를 감당할 가치가 있는가

covering index는 “가능하면 다 하자”가 아니라, 읽기 이득이 분명한 경로에 좁게 적용하는 전략이 보통 더 좋습니다.


8. EXPLAIN 에서 무엇을 보면 좋을까

covering index 여부를 볼 때 실무에서 자주 보는 신호 중 하나가 EXPLAINExtra 컬럼입니다.

특히:

  • Using index

같은 표시가 나오면, 인덱스만으로 필요한 컬럼을 처리하고 있을 가능성을 의심해볼 수 있습니다.

물론 이 한 줄만 보고 끝내면 안 됩니다. 같이 봐야 할 것은:

  • key 에 기대한 인덱스가 잡혔는가
  • rows 가 충분히 작아졌는가
  • 정렬 비용이나 임시 테이블 비용이 남아 있는가

즉 covering index는 Using index 만 외우는 문제가 아니라, 실제로 row lookup 감소가 plan 전체에서 의미 있게 반영됐는지를 같이 보는 것이 중요합니다.

실행 계획 읽기는 MySQL EXPLAIN 가이드와 이어서 보면 가장 자연스럽습니다.


9. covering index와 인덱스 설계 전체는 분리해서 보면 안 된다

covering index를 별도 기술처럼 보면 오해가 생깁니다. 실제로는 인덱스 설계 전체의 연장선입니다.

예를 들어 이 순서가 중요합니다.

  1. 먼저 어떤 쿼리를 빠르게 만들지 정합니다.
  2. 그 쿼리의 WHERE, JOIN, ORDER BY 를 지원하는 접근 경로를 설계합니다.
  3. 그 위에서 필요한 결과 컬럼 몇 개를 추가로 커버할 가치가 있는지 봅니다.

즉 covering index는 인덱스 설계의 첫 질문이 아니라, 대개 좋은 접근 경로를 잡은 뒤 “row lookup까지 줄일 수 있나”를 묻는 두 번째 질문입니다.

그래서 방금 리라이트한 MySQL 인덱스 설계 가이드와 가장 강하게 연결됩니다.


10. 자주 하는 오해

1. 인덱스가 있으면 다 covering index다

아닙니다. 필요한 컬럼이 인덱스 안에 다 있어야 covering 효과를 볼 수 있습니다.

2. Using index 만 뜨면 무조건 최고다

row 수가 여전히 크거나, 인덱스가 과하게 넓어져 쓰기 비용이 커질 수 있습니다.

3. SELECT * 도 covering index로 잘 해결된다

대부분의 경우 현실적이지 않습니다. 특히 큰 row, 많은 컬럼, 본문 데이터가 있는 테이블에서는 더 그렇습니다.

4. covering index는 읽기 최적화니까 많이 만들수록 좋다

읽기에는 이득이 있어도, 인덱스 크기와 write overhead가 같이 커집니다.


FAQ

Q. covering index는 어디에서 가장 효과가 큰가요?

자주 실행되는 목록 조회, 적은 컬럼만 반환하는 API, 필터와 정렬 패턴이 안정적인 읽기 쿼리에서 특히 효과가 큽니다.

Q. covering index인지 어떻게 확인하나요?

EXPLAINExtra, key, rows 를 함께 보고, 실제 latency가 줄었는지도 같이 확인하는 편이 좋습니다.

Q. 초보자는 무엇부터 이해하면 좋을까요?

일반 인덱스는 “찾는 역할”, covering index는 “찾고 필요한 값을 돌려주는 역할”이라는 차이부터 이해하면 금방 감이 옵니다.


먼저 읽어볼 가이드

검색 유입이 많은 핵심 글부터 이어서 보세요.

광고