인덱스, 만들면 정말 쓰일까

인덱스, 만들면 정말 쓰일까

June 24, 2026

SQL 튜닝 서비스의 “권고 Index 검증” 화면을 맡으면서 SQL 인덱스를 처음 들여다봤어요.
LLM이 권고한 인덱스가 실제로 만들어졌고 쓰이는지를 표로 보여주는 화면인데, 정작 인덱스가
뭔지 몰라서 0에서부터 정리했습니다.

인덱스를 걸면 빨라진다고만 알았는데, 만들어도 DB가 그 인덱스를 꼭 쓰는 건 아니더라고요.
검증 화면이 보여주는 것도 결국 이 둘, 만들어졌는지와 쓰이는지였습니다.


인덱스 = 책 뒤의 “찾아보기(색인)”

500쪽짜리 책에서 “옵티마이저”라는 단어가 나오는 곳을 찾는다고 해봅니다.

  • 인덱스 없음: 1쪽부터 500쪽까지 다 넘겨봅니다. → 책이 두꺼울수록 오래 걸립니다.
  • 인덱스 있음: 책 뒤 “찾아보기”에서 옵티마이저 ... 312, 401를 보고 그 쪽만 폅니다.

DB 테이블도 똑같습니다. 회원 1,000만 명이 든 users 테이블에서 WHERE name = '홍길동'으로
한 명을 찾는다고 해봅니다.

  • 인덱스가 없으면 1,000만 행을 처음부터 끝까지 다 읽으면서 이름이 같은지 하나씩
    비교합니다. 이걸 **Full Table Scan(풀 테이블 스캔)**이라고 합니다. 행이 늘수록 선형으로
    느려집니다.
  • name에 인덱스가 있으면, 이름순으로 미리 정렬된 인덱스를 타고 '홍길동' 위치로 바로
    점프합니다. 1,000만 행이든 1억 행이든 몇 번의 점프로 끝납니다.

인덱스는 특정 컬럼으로 행을 빨리 찾기 위해 미리 만들어 두는, 정렬된 찾아보기
자료구조
입니다. 정렬돼 있으니까 전부 안 보고도 원하는 위치를 좁혀 들어갈 수
있습니다.

실제 인덱스는 대부분 B-tree(B 트리) 라는 자료구조로 만들어집니다. 값이 정렬된 채로
여러 층의 노드에 나뉘어 담기는데, 위 노드의 숫자들이 “이 값은 어느 가지로 내려가면
되는지”를 가리키는 이정표 역할을 합니다.

조금 더 들여다보면, 위쪽 노드(inner node)는 이정표(키 + 자식 포인터)만 들고 있고, 맨 아래
노드(leaf node)에 실제 키와 값이 정렬된 채 놓여 있어요.

WHERE name = '홍길동'을 찾는다면, 위에서부터 “홍길동이 이 범위에 있나”를 몇 번 비교하며
타고 내려가 leaf의 정확한 위치로 점프합니다. 전부 훑지 않고 트리 높이만큼만 내려가면 되니,
행이 1,000만이든 1억이든 비교 횟수는 몇 번 차이 안 납니다.

조회(lookup) 복잡도로 비교하면 차이가 분명합니다. 정렬 안 된 데이터를 통째로 훑는 건
O(n)이지만, B-tree는 O(log n)이에요. 행이 늘어도 비교 횟수가 거의 안 늘어나는 게 이
log의 힘입니다.

🤔 DB(데이터베이스)란? 데이터를 정리해서 저장·조회하는 시스템. 거대한 엑셀 묶음을
안전하고 빠르게 다루는 프로그램이라고 생각하면 시작점으로 충분합니다.
테이블 / 행(row) / 컬럼(column) = 테이블은 엑셀 시트 한 장, 행은 가로 한 줄(데이터
1건, 예: 회원 한 명), 컬럼은 세로 한 칸의 항목(예: name, email).

🤔 풀 테이블 스캔(Full Table Scan)이란? 인덱스 없이 테이블을 처음부터 끝까지 다
읽는 것. 데이터가 많을수록 느립니다.

만드는 법

CREATE INDEX idx_users_name ON users(name);

users 테이블의 name 컬럼에 대한 인덱스를 idx_users_name이라는 이름으로 만들어라”는
뜻입니다. 이렇게 테이블·인덱스 같은 데이터 구조 자체를 정의·생성하는 SQLDDL
(Data Definition Language)이라고 부릅니다. 데이터를 넣고 빼는 SELECT/INSERT(DML)와는
구분되는 부류예요. 뒤에서(2편에서) “DDL을 복사한다”는 말이 나오는데, 바로 이
CREATE INDEX ... 문장 전체를 가리킵니다.

🤔 SQL이란? DB에게 “이거 찾아줘 / 넣어줘 / 만들어줘”라고 시키는 명령 언어.
데이터를 찾는 한 건의 명령(SELECT ...)을 **쿼리(query)**라고 부릅니다.

🤔 DDL / DML이란? SQL 명령을 역할로 나눈 분류예요.
DDL(Data Definition Language) = 테이블·인덱스 같은 구조를 만드는 명령
(CREATE, ALTER, DROP …). CREATE INDEX ...가 바로 DDL입니다.
DML(Data Manipulation Language) = 그 구조 안의 데이터를 넣고 빼고 찾는 명령
(SELECT, INSERT, UPDATE, DELETE). 2편에서 “DDL을 복사한다”는 건 이 CREATE INDEX
문장 전체를 복사한다는 뜻이에요.

공짜가 아닙니다 - 인덱스의 비용

인덱스가 항상 좋기만 하면 모든 컬럼에 다 걸면 되겠지만, 비용이 있습니다.

  1. 저장 공간을 더 씁니다. 인덱스도 디스크에 저장되는 별도 데이터입니다. 컬럼 하나를
    통째로 정렬해서 복제해 두는 셈이라, 큰 테이블이면 인덱스 용량도 무시 못 합니다.
  2. 쓰기가 느려집니다. 행을 INSERT/UPDATE/DELETE 할 때마다 본문뿐 아니라 인덱스도 같이
    고쳐야 합니다. 책에 문장을 추가할 때마다 뒤 찾아보기의 쪽번호도 갱신해야 하는 것과
    같아요. 인덱스가 10개 걸린 테이블은 INSERT 한 번에 인덱스 10개를 동시에 갱신합니다.

결국 “어떤 컬럼에 인덱스를 거는 게 이득인가”가 곧 SQL 튜닝의 핵심 주제 중 하나입니다.
조회는 빨라지지만 쓰기는 느려지니, 무턱대고 다 거는 게 답이 아니에요. 제가 작업하는
서비스는 바로 이 판단을 LLM이 분석해서 “이 쿼리엔 이런 인덱스를 만들면 빨라집니다”라고
권고
해줍니다.

🤔 LLM이란? 대규모 언어 모델(ChatGPT 같은 AI). 이 서비스에선 LLM이 SQL을 분석해
“이런 인덱스 만들면 빨라집니다”를 권고하는 역할을 합니다.

옵티마이저와 EXPLAIN PLAN - “DB가 정말 인덱스를 쓰는가”

여기서 가장 중요한 함정이 하나 있습니다. 초보가 제일 헷갈리는 지점이기도 해요.

인덱스를 만들었다고 해서, DB가 그 인덱스를 꼭 쓰는 건 아닙니다.

쿼리를 어떻게 처리할지 — 테이블을 다 읽을지(Full Scan), 어떤 인덱스를 탈지,
여러 테이블을 어떤 순서로 조인할지 — 를 결정하는 DB 내부의 두뇌가 옵티마이저(Optimizer)
입니다. 옵티마이저는 “이 테이블에 행이 몇 개고, 값이 얼마나 다양한가” 같은 통계 정보
보고 “이 방법이 제일 싸겠다”를 스스로 고릅니다.

DB가 쿼리를 받아 실제로 돌리기까지의 흐름을 보면, 파싱을 거친 뒤 옵티마이저가 여러 실행
계획 후보를 만들어 그중 하나를 고르는 Optimization 단계가 따로 있습니다. 내가 만든
인덱스를 쓸지 말지가 바로 여기서 갈립니다.

🤔 옵티마이저(Optimizer)란? 쿼리를 “실제로 어떻게 처리할지”(인덱스를 탈지, 다 읽을지
등)를 스스로 정하는 DB 내부의 두뇌. 내가 인덱스를 만들어도 쓸지 말지는 얘가 정합니다.
통계(statistics) = “행이 몇 개고 값이 얼마나 다양한지” 같은 요약 정보로, 옵티마이저가
판단의 근거로 삼습니다. 오래되면 잘못된 판단을 합니다.

그러다 보니 이런 일이 벌어집니다.

  • 인덱스를 만들어도 옵티마이저가 “이 경우엔 그냥 다 읽는 게 더 빠르겠는데” 하고 무시합니다.
    (예: 테이블이 작거나, 조건에 맞는 행이 너무 많으면 인덱스 점프보다 통독이 쌉니다.)
  • 통계가 오래돼 실제와 어긋나면, 옵티마이저가 잘못 판단해 인덱스를 안 씁니다.

옵티마이저가 짠 처리 계획(=실행계획)을 실행 전에 미리 들여다보는 명령이
EXPLAIN PLAN입니다. “이 쿼리, 돌리면 어떤 순서로 어떤 인덱스를 쓸 거야?”를
쿼리를 진짜 실행하지 않고 물어보는 거예요. 그 계획 안에 우리가 만든 인덱스 이름이
등장하면 “옵티마이저가 이 인덱스를 쓰기로 했다”는 증거가 됩니다.

🤔 실행계획(Execution Plan)이란? 옵티마이저가 짠 “이 쿼리를 이런 순서로 이렇게
처리하겠다”는 설계도. EXPLAIN PLAN은 그 설계도를 쿼리를 실제로 돌리지 않고 미리
보여달라는 명령입니다 — “이 쿼리, 인덱스 쓸 거야?”를 확인할 때 씁니다.

권고 인덱스가 효과가 있었는지 확인하려면 두 가지를 따로 봐야 합니다.

무엇을 묻나어떻게 확인하나(2편) 코드의 필드
인덱스가 실제로 만들어졌나?카탈로그(ALL_INDEXES)에 그 이름이 있나created
옵티마이저가 그걸 쓰나?EXPLAIN PLAN에 그 인덱스가 등장하나utilized

ALL_INDEXES는 Oracle이 “이 계정이 접근 가능한, 지금 존재하는 모든 인덱스 목록”을 담아
두는 시스템 테이블입니다. “그 이름의 인덱스가 진짜 존재하나?”는 결국 이 목록에 이름이
있나 없나로 판정됩니다.

🤔 Oracle(오라클)이란? DB 제품 중 하나(브랜드 이름). 기업에서 많이 쓰는 상용 DB로,
이 서비스의 튜닝 대상이 Oracle입니다.
데이터 딕셔너리 = DB가 자기 구조(어떤 테이블·인덱스·컬럼이 있는지)를 스스로 기록해
둔 메타데이터 모음. **ALL_INDEXES**는 그중 “지금 존재하는 인덱스 목록”을 담은 테이블로,
권고 인덱스가 진짜 만들어졌는지(created)를 여기서 확인합니다.

INVISIBLE 인덱스 - “운영에 영향 없이 테스트하기”

운영 중인 DB에 인덱스를 함부로 만들면 위험합니다. 옵티마이저가 갑자기 그 새 인덱스를
써버려서, 멀쩡하게 잘 돌던 다른 쿼리들의 실행계획이 바뀌고 느려질 수 있기 때문이에요.
“이 쿼리 빠르게 하려다 옆 쿼리 10개 느려지는” 사고가 실제로 납니다.

이걸 막으려고 Oracle엔 INVISIBLE 인덱스라는 안전장치가 있습니다.

CREATE INDEX idx_users_name ON users(name) TABLESPACE ... INVISIBLE;
  • 실제로 만들어지고, 데이터가 바뀌면 인덱스도 정상적으로 갱신됩니다(= 진짜 인덱스입니다).
  • 하지만 옵티마이저는 기본적으로 이 인덱스가 안 보이는 것처럼 무시합니다. 그래서
    운영 쿼리들의 계획은 그대로 유지됩니다 — 영향이 없어요.
  • 검증하고 싶은 내 세션에서만 OPTIMIZER_USE_INVISIBLE_INDEXES = TRUE를 켜면, 그
    세션 안에서만 옵티마이저가 이 인덱스를 후보로 봅니다. 그 상태로 EXPLAIN PLAN을 떠보면
    “이 인덱스를 쓰면 계획이 어떻게 바뀌나”를 실제로 확인할 수 있습니다.

운영 쿼리엔 영향 없이, 게다가 드롭(삭제)도 없이, 인덱스 효과를 미리 시험할 수
있습니다. 이게 운영 DB 튜닝에서 꽤 강력한 패턴이에요. 그래서 권고 DDL 끝에 INVISIBLE
붙어 있고, 검증은 “이 인덱스를 보이게 한 세션에서의 EXPLAIN”으로 돌아갑니다.

🤔 세션(session)이란? 사용자가 DB에 접속해 있는 하나의 연결. “내 세션에서만 켠다”는
건 나만의 접속에서만 적용하고 다른 사람 접속엔 영향 안 준다는 뜻입니다.
TABLESPACE = Oracle에서 데이터(인덱스 포함)를 어느 저장 공간에 둘지 지정하는 단위.
권고 DDL에 같이 붙어 오지만, 깊게 몰라도 이 글 이해엔 지장 없습니다.


1편에서 얻어간 것

  • 인덱스 = 정렬된 찾아보기. 조회는 빨라지지만 저장·쓰기 비용이 있어서, 어디 거느냐가
    곧 튜닝입니다.
  • 만든 것과 쓰이는 것은 다릅니다. 쓸지 말지는 옵티마이저가 통계를 보고 결정하고, 그
    결정은 EXPLAIN PLAN으로 들여다봅니다. → 이게 다음 편 코드의 created/utilized
    질문이 됩니다.
  • INVISIBLE 인덱스 = 운영 영향 없이, 드롭 없이 효과를 테스트하는 안전장치. 그래서 권고
    DDL에 INVISIBLE이 박혀 있습니다.

용어 정리

이 글에 나온 용어를 한곳에 모았습니다.

용어한 줄 정의
인덱스(Index)특정 컬럼으로 행을 빨리 찾으려고 미리 만들어 두는, 정렬된 찾아보기 자료구조
B-tree인덱스를 실제로 떠받치는 자료구조. 정렬된 값을 여러 층 노드로 나눠 담아 O(log n)에 찾는다
Full Table Scan인덱스 없이 테이블을 처음부터 끝까지 다 읽는 것. 데이터가 많을수록 느리다
DDL테이블·인덱스 같은 구조를 만드는 SQL(CREATE, ALTER, DROP). CREATE INDEX가 여기 속한다
DML데이터를 넣고 빼고 찾는 SQL(SELECT, INSERT, UPDATE, DELETE)
옵티마이저(Optimizer)쿼리를 실제로 어떻게 처리할지(인덱스를 탈지 말지)를 스스로 정하는 DB 내부의 두뇌
통계(Statistics)“행이 몇 개고 값이 얼마나 다양한지” 같은 요약 정보. 옵티마이저의 판단 근거
실행계획(Execution Plan)옵티마이저가 짠 “이 쿼리를 이렇게 처리하겠다”는 설계도
EXPLAIN PLAN실행계획을 쿼리를 실제로 돌리지 않고 미리 보여달라는 명령
ALL_INDEXESOracle이 “접근 가능한, 지금 존재하는 인덱스 목록”을 담아 두는 시스템 테이블
데이터 딕셔너리DB가 자기 구조(테이블·인덱스·컬럼)를 스스로 기록해 둔 메타데이터 모음
INVISIBLE 인덱스실제로 만들어지되 옵티마이저가 기본적으로 무시하는 인덱스. 운영 영향 없이 효과를 테스트할 때 쓴다
세션(Session)사용자가 DB에 접속해 있는 하나의 연결. “내 세션에서만 켠다”는 나만 적용한다는 뜻

2편에서는 이 도메인으로 실제 검증 컴포넌트를 읽습니다. created/utilized 두 축이 표가
되고, 복사 버튼이 왜 이름이 아니라 DDL을 복사하는지를 봅니다.

2편: 도메인을 알고 나니 컴포넌트가 읽혔습니다

On this page