바이브 코딩 시대의 데이터베이스6-2 언제 인덱스를 만들까
테크타니 LEARN — 섹션 06 · 인덱스

언제 인덱스를 만들까 —
WHERE · JOIN · ORDER BY 세 단서

6-1에서 인덱스가 무엇인지는 봤습니다. 그런데 책의 모든 단어를 찾아보기에 넣지는 않죠. 그럼 어떤 단어를 고를까요? 답은 컬럼 이름이 아니라 실제로 자주 실행하는 SQL 안에 있습니다.

난이도보통소요8분준비물6-1 완료오늘SQL 에서 후보 찾아내기

이번 섹션의 여정

6-1 인덱스의 본질

완료 ✓

6-2 언제 인덱스를 만들까

지금 여기

6-3 단일 · 복합 인덱스

컬럼 순서

6-4 ~ 6-5

EXPLAIN · 비용과 함정

찾아보기에 넣을 단어는 어디서 고를까

6-1에서 인덱스를 두꺼운 책 맨 뒤의 찾아보기에 비유했습니다. 그리고 마지막에 한 마디를 남겼죠 — 모든 단어를 찾아보기에 넣지는 않는다고요.

그럼 어떤 단어를 골라야 할까요? 책 본문의 목차를 다시 읽어봐야 답이 나오는 게 아닙니다. 사람들이 실제로 무엇을 찾는지를 봐야 해요.

데이터베이스도 똑같습니다. 인덱스 후보는 컬럼 이름과 중요도를 보고 고르는 것이 아니라 — 실제로 자주 실행하는 SQL 에서 찾습니다.

후보를 어디서 고르나
보는 곳판단
컬럼 이름과 중요도✕ 중요한 정보라고 자주 찾는 것은 아니다
테이블 정의서✕ 구조는 알려주지만 사용 습관은 알려주지 않는다
자주 실행하는 SQL✓ WHERE · JOIN · ORDER BY — 세 자리를 본다
구조를 아무리 들여다봐도 인덱스 후보는 안 보입니다 — 사용 습관에서 나오니까요
인덱스 후보 탐색 지도
자주 실행하는 SQL
├─ WHERE → 찾는 기준
├─ JOIN ... ON → 연결하는 기준
└─ ORDER BY → 정렬하는 기준
 
데이터 양 · 사용 빈도 · 줄어드는 작업 — 이 셋을 확인
⭐ 오늘의 핵심 문장

인덱스는 중요한 컬럼에 무조건 만드는 것이 아니라 — 자주 찾고, 연결하고, 정렬하는 길 가운데 확인하거나 정렬할 일을 많이 줄여 주는 곳에 만듭니다.

시작 전에 — shop 에는 이미 인덱스가 있습니다

후보를 고르기 전에 6-1에서 확인한 것을 한 번 더 짚고 갑니다. 우리 init.sql 에는 CREATE INDEX 도, INDEX 선언도 한 줄도 없습니다. 그런데 인덱스가 하나도 없는 건 아니에요. 기본키와 외래키가 이미 인덱스를 깔아 뒀고, 여기에 6-1에서 products.product_name 에 하나를 직접 만들었죠.

shop 에 이미 만들어져 있는 인덱스
테이블인덱스가 있는 컬럼어디서 생겼나
membersmember_id기본키
categoriescategory_id기본키
productsproduct_id / category_id / product_name기본키 / 외래키를 만들 때 자동 생성 / 6-1 에서 직접 생성
ordersorder_id / member_id기본키 / 외래키를 만들 때 자동 생성
order_items(order_id, product_id) / product_id복합 기본키 / 외래키를 만들 때 자동 생성
InnoDB 는 외래키 제약을 만들 때 자식 쪽 컬럼에 인덱스를 자동으로 만듭니다
'FK 컬럼에는 인덱스를 만들어라' — MySQL 에서는 이미 끝난 이야기

다른 데이터베이스를 기준으로 쓴 글에서 자주 보이는 조언이지만, MySQL(InnoDB)에서는 외래키 제약을 만들 때 쓸 인덱스가 없으면 자식 컬럼에 인덱스가 자동으로 생깁니다. orders.member_id, products.category_id, order_items.product_id 는 그래서 이미 인덱스를 갖고 있어요. 같은 컬럼에 인덱스를 하나 더 만들면 — 얻는 것 없이 저장 공간과 관리 비용만 늘어납니다.

order_items.order_id 는 왜 목록에 따로 없을까

order_items 의 기본키는 (order_id, product_id) 복합키입니다. order_id맨 왼쪽 컬럼이라 기본키 인덱스가 그대로 커버해요. 그래서 따로 인덱스를 만들지 않습니다. 맨 왼쪽 컬럼이 왜 특별한지는 6-3 에서 다룹니다.

그러니까 오늘 후보를 고를 무대는 — 아직 인덱스가 없는 컬럼들입니다.

지금 인덱스가 없는 컬럼
테이블인덱스가 없는 컬럼
membersmember_name · gender · age · joined_at
categoriescategory_name
productsprice
ordersordered_at
order_itemsquantity
이 중에서 세 단서에 걸리는 컬럼을 찾아봅니다

첫 번째 단서 — WHERE, 찾는 기준

WHERE 는 많은 행 중에서 원하는 것만 골라내는 자리입니다. 5-2 에서 내내 썼던 그 절이죠.

-- 5-2 에서 실행했던 쿼리
SELECT
  *
FROM
  members
WHERE
  age >= 30;

회원 20명 중 12명이 통과했습니다. 여기서 검색 기준으로 쓰인 컬럼은 age 하나예요. 이런 조회를 매일 한다면 members.age 가 후보로 올라옵니다.

주문 쪽은 더 현실적입니다. 실무에서 하루에도 몇 번씩 나오는 질문 — “올해 주문만 보여줘.”

SELECT
  *
FROM
  orders
WHERE
  ordered_at BETWEEN '2026-01-01' AND '2026-12-31';

주문 40건 중 11건이 남습니다. 주문일이 계속 검색 기준으로 쓰이니 orders.ordered_at 이 후보예요.

WHERE 에서 읽어낸 후보
자주 실행하는 질문WHERE 에 쓰이는 컬럼지금 인덱스
30세 이상 회원만 보고 싶다members.age없음 → 후보
올해 주문만 보고 싶다orders.ordered_at없음 → 후보
5만 원 이상인 상품만 보고 싶다products.price없음 → 후보
김철수 회원의 주문만 보고 싶다orders.member_id있음 (외래키 자동)
상품 이름으로 찾고 싶다products.product_name있음 (6-1 에서 직접 생성)
같은 WHERE 라도 — 이미 인덱스가 있는 컬럼은 더 만들 일이 없습니다

다만 SQL 에 한 번 등장했다고 바로 후보가 되는 것은 아닙니다. 두 가지가 같이 성립해야 해요.

같은 WHERE 조건인데 왜 하나만 후보일까

✕ 후보로 보기 어려운 조건
  • 어쩌다 한 번연말에만 돌려보는 조회

  • 거의 안 줄어든다조건을 걸어도 대부분의 행이 그대로 남는다

  • 결과찾아보기를 펴는 수고가 그냥 넘겨보는 것보다 클 수 있다

✓ 좋은 후보
  • 매일 수백 번같은 형태의 조건이 반복해서 실행된다

  • 많이 줄어든다조건 하나로 확인할 행이 크게 줄어든다

  • 결과확인할 일이 줄어든 만큼 이득이 남는다

인덱스는 자주 실행되고, 확인할 일을 많이 줄여 주는 조건에서 값을 합니다.

두 번째 단서 — JOIN, 연결하는 기준

두 번째 단서는 JOIN 입니다. 조인은 서로 다른 두 테이블을 연결하죠. 5-6 에서 회원표와 주문표를 이었던 쿼리입니다.

SELECT
  m.member_name,
  o.order_id,
  o.ordered_at
FROM
  members m
INNER JOIN orders o
  ON m.member_id = o.member_id;

ON 뒤에 적힌 두 컬럼이 연결의 기준입니다. 데이터베이스는 한쪽 행마다 반대쪽에서 짝을 찾아야 하니, 연결에 쓰는 컬럼은 자연스럽게 인덱스 후보가 돼요.

그런데 여기서 중요한 말은 후보입니다. 조인에 보인다고 무조건 새 인덱스를 만든다는 뜻은 아니에요. 5-6 의 4테이블 체인으로 확인해 보겠습니다.

-- 회원 → 주문 → 주문상세 → 상품
SELECT
  m.member_name,
  o.ordered_at,
  p.product_name,
  oi.quantity
FROM
  members m
JOIN orders o
  ON m.member_id = o.member_id
JOIN order_items oi
  ON o.order_id = oi.order_id
JOIN products p
  ON oi.product_id = p.product_id;
JOIN 체인의 ON 컬럼 — 지금 무엇이 받쳐주고 있나
ON 조건왼쪽 컬럼오른쪽 컬럼
m.member_id = o.member_idmembers 기본키orders 외래키 자동 인덱스
o.order_id = oi.order_idorders 기본키order_items 복합 기본키의 맨 왼쪽
oi.product_id = p.product_idorder_items 외래키 자동 인덱스products 기본키
세 줄 전부 — 새로 만들 인덱스가 없습니다
⭐ JOIN 단서를 정확히 쓰는 법

JOIN 은 후보 자리를 알려주지만, MySQL 에서는 그 자리가 이미 채워져 있는 경우가 많습니다.

그래서 순서는 언제나 — 만들기 전에 이미 있는지부터 확인.

그럼 JOIN 단서는 언제 쓸모가 있나

외래키 제약 없이 두 테이블을 이어 쓰는 설계에서는 이야기가 완전히 달라져요. 연결에 쓰는 컬럼에 아무 인덱스도 없다면, 짝을 찾을 때마다 반대쪽 테이블을 훑게 됩니다. 그럴 때는 ON 에 적힌 컬럼이 1순위 후보예요.

세 번째 단서 — ORDER BY와 LIMIT, 정렬하는 기준

세 번째 단서는 ORDER BY 입니다. 5-3 에서 만든 그 패턴이죠.

-- 최근 가입자 5명
SELECT
  *
FROM
  members
ORDER BY
  joined_at DESC
LIMIT 5;

회원 20명을 가입일 순으로 줄 세운 뒤 위에서 5명만 가져옵니다. 결과는 5행인데 — 줄을 세우는 일은 20명 전체에 대해 일어납니다. 결과 행 수와 실제로 한 일이 다르다는 건 6-1에서 이미 본 이야기예요.

이게 ORDER BY 가 단서가 되는 이유입니다. 인덱스는 값이 정렬된 상태로 정리되어 있어요. 가입일 순서로 정리된 인덱스를 따라갈 수 있다면, 데이터베이스는 앞에서 5개만 읽고 멈출 수도 있습니다. 별도로 정렬하는 일을 줄일 수 있는 거죠.

주문 쪽도 모양이 같습니다.

-- 최근 주문 10건
SELECT
  *
FROM
  orders
ORDER BY
  ordered_at DESC
LIMIT 10;

주문 40건을 줄 세워 10건만 가져옵니다. orders.ordered_at 이 여기서 또 나왔죠 — WHERE 에서 한 번, ORDER BY 에서 또 한 번. 같은 컬럼이 두 단서에 겹쳐 나오면 후보 순위가 올라갑니다.

LIMIT 이 있느냐 없느냐
쿼리실제로 하는 일인덱스로 줄어드는 정도
ORDER BY joined_at DESC LIMIT 5앞에서 5행만 읽고 멈출 수 있다
ORDER BY joined_at DESC (LIMIT 없음)어차피 20행 전부 결과로 나간다작음
정렬 단서는 LIMIT 과 함께일 때 가장 강합니다 — 앞 몇 개만 필요할 때
ORDER BY 가 보인다고 인덱스가 정렬을 항상 대신해 주지는 않습니다

인덱스로 정렬을 건너뛸 수 있는지는 정렬 컬럼, 정렬 방향, 앞에 붙은 다른 조건에 따라 달라집니다. 데이터베이스가 실제로 어떤 길을 골랐는지는 짐작하지 말고 확인해야 해요. 그 도구가 EXPLAIN 이고, 읽는 법은 6-4 에서 다룹니다.

반대로 — 효과가 작을 수 있는 세 가지 경우

단서를 찾는 눈만큼 중요한 게 우선순위를 낮추는 눈입니다. 세 가지 상황을 보겠습니다.

첫째, 찾는 조건으로 거의 쓰지 않는 컬럼. order_items.quantity 를 생각해 보세요. 수량은 분명 중요한 정보입니다. 매출을 계산하려면 반드시 필요하죠. 그런데 “수량이 2인 주문상세만 찾아줘” 같은 조회를 매일 하나요? 거의 안 합니다. 중요한 정보인 것과, 찾는 기준으로 자주 쓰는 것은 다른 이야기예요.

둘째, 데이터가 아주 적은 테이블. categories 는 다섯 행입니다. 5쪽짜리 책에 찾아보기를 붙이는 셈이에요. 그냥 처음부터 읽어도 부담이 거의 없습니다.

솔직히 말하면 — shop 은 전체가 얇은 책입니다

회원 20 · 상품 30 · 주문 40 · 주문상세 64. 이 크기에서는 어떤 인덱스를 만들어도 체감 차이를 만들기 어렵고, 6-1에서 본 것처럼 데이터베이스가 인덱스를 만들어 놓고도 전체 스캔을 고를 수 있어요. 그래서 이 강의에서 얻어 갈 것은 지금 만들 인덱스가 아니라 — 데이터가 수십만 행이 됐을 때 어디를 볼지 아는 눈입니다.

셋째, 조건을 걸어도 결과가 거의 줄지 않는 경우. members.gender 는 값이 'M''F' 둘뿐입니다. 우리 shop 은 남성 10명, 여성 10명 — 정확히 반씩이에요.

gender 로 거르면 얼마나 줄어들까
조건남는 행전체 대비
gender = 'F'1020명 중 절반
gender = 'M'1020명 중 절반
찾아보기를 펼쳐 절반을 확인하느니 — 그냥 처음부터 읽는 편이 나을 수 있습니다

값의 종류가 적으면 조건 하나로 걸러도 많은 행이 그대로 남습니다. 인덱스를 거친 다음 결국 그 많은 행을 다시 확인해야 하니, 이득이 작아지는 거예요.

'절대 만들지 마라'는 뜻이 아닙니다

값의 종류가 적은 컬럼도 다른 컬럼과 함께 묶으면 쓸모가 생길 수 있습니다. 예를 들어 “여성 회원을 가입일 최신순으로” 같은 요구가 매일 반복된다면 genderjoined_at 을 하나로 묶는 선택지가 생겨요. 여러 컬럼을 묶는 방법은 바로 다음 6-3 에서 다룹니다. 오늘은 “좋은 후보인지 한 번 더 생각해 보라” 정도로만 기억하세요.

후보를 만나면 물어볼 세 가지 질문

여기까지를 세 질문으로 압축할 수 있습니다.

인덱스 후보 판단 3문
① 데이터가 많은가? — 몇 십 행짜리 테이블이라면 전체를 읽어도 부담이 작다
② 자주 실행되는가? — 한 번 쓰고 마는 조회가 아니라 매일 반복되는 조회인가
③ 찾거나 정렬하는 일이 많이 줄어드는가? — 조건을 걸었을 때 확인할 행이 크게 줄어드는가

셋 다 “예”라면 만들 이유가 분명합니다. 하나라도 “아니오”라면 — 만들지 말라는 뜻이 아니라 한 번 더 생각해 보라는 신호예요.

그리고 그 후보는 SQL 의 세 자리에서 찾습니다.

세 단서 — 무엇을 알려주나
SQL 의 자리알려주는 것shop 에서 본 예
WHERE찾는 기준orders.ordered_at · members.age
JOIN ... ON연결하는 기준m.member_id = o.member_id (이미 인덱스 있음)
ORDER BY (+ LIMIT)정렬하는 기준members.joined_at · orders.ordered_at
찾고 · 연결하고 · 정렬할 때 자주 쓰는 컬럼 — 후보는 전부 여기서 나옵니다
✓ 순서까지 함께 기억하세요

SQL 에서 세 단서를 찾고 → 세 질문으로 거르고 → 이미 있는지 확인한다. 컬럼 이름만 보고 “이건 중요하니까 인덱스” 라고 결정하는 순간부터 잘못된 인덱스가 쌓이기 시작합니다.

한눈에 정리

구분무엇을 보나shop 에서는
단서 ① WHERE자주 거르는 컬럼orders.ordered_at · members.age · products.price
단서 ② JOIN … ON연결에 쓰는 컬럼기본키 · 외래키 자동 인덱스가 이미 전부 커버
단서 ③ ORDER BY + LIMIT정렬해서 앞 몇 개만 뽑는 컬럼members.joined_at · orders.ordered_at
효과가 작을 때안 찾는 컬럼 · 작은 테이블 · 거의 안 줄어드는 조건order_items.quantity · categories(5행) · members.gender
세 가지 질문많은가 · 자주인가 · 많이 줄어드는가셋 다 “예”일 때 만들 이유가 분명해진다

인덱스 후보는 컬럼의 중요도가 아니라 사용 습관에서 나옵니다. 자주 찾고, 자주 연결하고, 자주 정렬하는 컬럼 — 그리고 그 일을 실제로 많이 줄여 줄 수 있는 자리. 우리 shop 처럼 작은 데이터베이스에서는 이미 있는 인덱스만으로 충분하다는 것도 오늘 함께 확인했습니다.

그런데 조건에 컬럼이 하나만 나오는 경우는 드물죠. “여성 회원을 가입일 최신순으로” 처럼 두 컬럼이 함께 등장하면 — 인덱스를 두 개 만들까요, 하나로 묶을까요? 묶는다면 어느 컬럼을 앞에 둘까요?

NEXT LESSON6-3 단일·복합 인덱스 — 컬럼 순서가 중요한 이유

여러 컬럼을 묶은 인덱스에서 왜 왼쪽 컬럼부터 중요한지 — 순서를 정하는 기준을 봅니다.

GO ▸ 6-3 시작하기