본문 바로가기

DB

[DB] 인덱스 (Index)

❌ 인덱스의 필요성: Full Table Scan의 한계

 

데이터베이스 성능 저하의 가장 흔한 원인은 '풀 테이블 스캔(Full Table Scan)'이다.

풀 테이블 스캔은 말 그대로 테이블의 전체 행(Row)을 처음부터 마지막까지 하나씩 차례대로 스캔하는 것이다.

데이터베이스는 특정 컬럼에 특정 값이 어느 행에 있는지 알 수 없기 때문에, 해당 테이블 전체를 디스크에서 메모리로 읽어 들여 풀 테이블 스캔을 하는 것이다.

 

풀 테이블 스캔의 시간 복잡도는 데이터 건수에 비례해 검색 시간이 증가한다.

데이터가 적을 때는 문제가 되지 않지만, 데이터가 수백만, 수천만 건으로 증가하면 애플리케이션의 응답 속도에 치명적인 영향을 미친다.

 

또한 실무적 관점에서 이는 단순한 연산 횟수 증가를 넘어, 디스크 I/O(Input/Output) 비용의 급증을 의미한다.

  • 디스크 접근의 비용:
    • 데이터베이스는 기본적으로 디스크에 데이터를 저장한다.
    • 디스크에서 데이터를 읽는 작업은 메모리에서 읽는 것보다 수만 배에서 수십만 배 느리다.
    • 풀 테이블 스캔은 테이블 전체에 해당하는 데이터 블록을 디스크에서 읽어 메모리(버퍼 풀)로 가져오는 과정을 수반하므로, I/O 병목 현상의 주범이 된다.
  • 버퍼 풀 오염:
    • 대용량 테이블을 스캔하면 데이터베이스의 메모리 캐시 영역인 '버퍼 풀'이 해당 테이블의 데이터 페이지로 가득 차게 된다.
    • 이로 인해 자주 사용되어야 할 다른 중요한 데이터나 인덱스 페이지가 메모리에서 밀려나는 '캐시 오염'이 발생하여, 시스템 전반의 성능 저하를 유발할 수 있다.

 

이처럼 풀 테이블 스캔은 단순히 해당 쿼리 하나를 느리게 만드는 것을 넘어, 시스템 전체의 안정성을 위협하는 고비용 작업이다.

따라서 서비스의 핵심 기능에서 풀 테이블 스캔을 방지하는 것은 필수적이다.

 


🧩 인덱스의 개념과 내부 구조

 

인덱스는 책의 '찾아보기'와 같이 원하는 데이터를 빠르게 찾기 위해 별도로 생성하는 정렬된 자료구조.

 

인덱스는 주로 B-Tree 또는 그 변형인 B+Tree 자료구조로 구현된다.

B-Tree가 데이터베이스 환경에 최적화된 이유는 다음과 같다.

 

  • I/O 최적화:
    • 이진 트리는 하나의 노드에 하나의 데이터만 저장하지만, B-Tree는 하나의 노드(페이지)에 여러 개의 데이터(Key)와 자식 노드 포인터를 저장할 수 있다.
    • 이는 트리의 높이를 극단적으로 낮게 유지하여, 루트 노드에서 리프 노드까지 찾아가는 동안 발생하는 디스크 I/O 횟수를 획기적으로 줄여준다.
  • 항상 균형 유지:
    • B-Tree는 데이터의 삽입/삭제 시 자체적으로 균형을 맞추는 로직이 포함된 '밸런스 트리'의 일종이다.
    • 이 덕분에 데이터 분포에 상관없이 최악의 경우에도 O(log N)의 검색 성능을 보장한다.
이진 탐색 트리도 O(log N)이지만, 데이터가 순차적으로 입력되면 균형이 한쪽으로 치우쳐져 최악의 경우 O(N)이 될 수 있다.

 

 


📌 MySQL의 인덱스 구조: 클러스터형과 보조 인덱스

 

MySQL의 경우 클러스터형(Clustered) 인덱스와 보조(Secondary) 인덱스를 제공한다.

 

  • 클러스터형 인덱스:
    • 테이블당 하나만 존재하며, 보통 기본 키(PK)에 대해 생성된다.
    • 이 인덱스는 원본 데이터 자체를 리프 노드에 저장하며, 인덱스의 정렬 순서가 곧 데이터의 물리적 저장 순서가 된다.
    • 따라서 PK를 통한 조회는 매우 빠르다.
  • 보조 인덱스:
    • PK가 아닌 다른 컬럼에 생성하는 일반적인 인덱스다.
    • 보조 인덱스의 리프 노드는 인덱스 키 값과 함께, 해당 데이터가 저장된 곳을 가리키는 기본 키(PK) 값을 가지고 있다.
    • 따라서 보조 인덱스를 통한 검색은 다음과 같은 2단계 과정을 거친다.
      1. 보조 인덱스에서 조건에 맞는 PK 값을 찾는다.
      2. 찾아낸 PK 값을 사용해 클러스터형 인덱스에서 최종 데이터 행을 조회한다.
    • 이 구조 때문에 보조 인덱스를 사용하는 쿼리는 추가적인 오버헤드가 발생할 수 있다.

 


🏭 인덱스 생성 / 삭제 / 확인

-- 인덱스 생성
CREATE INDEX idx_index_name ON table_name (column1, column2, ...);
  • 인덱스 이름은 관례적으로 idx_테이블명_컬럼명으로 짓는다.
-- 인덱스 삭제
DROP INDEX idx_index_name ON table_name;
  • 인덱스 삭제는 생성할 때와 비슷하다.
-- 인덱스 확인
SHOW INDEX FROM table_name;
  • 해당 테이블에 설정된 모든 인덱스의 정보를 조회할 수 있다.
  • 주요 컬럼은 다음과 같다.
Table 인덱스가 정의된 테이블 이름
Non_unique 인덱스 중복 허용 여부 (0: 고유(UNIQUE, PK) 인덱스, 1: 일반 인덱스)
Key_name 인덱스 이름 (PRIMARY: 기본 키 인덱스)
Seq_in_index 인덱스 내에서 컬럼 순서 (1부터 시작) → 복합 인덱스일 경우 컬럼의 위치를 나타냄
Column_name 인덱스를 구성하는 컬럼 이름
Cardinality 인덱스가 가진 고유 값의 추정 개수 (값이 높을수록 검색 효율이 좋음) → 옵티마이저가 실행 계획 세울 때 참고

 

⚠️ 중요: MySQL은 UNIQUE, PK, FK 컬럼에 대해 자동으로 인덱스를 생성한다.

 


⌥ 손익분기점: 옵티마이저의 선택

 

인덱스를 생성한다고 해서 데이터베이스가 항상 그 인덱스를 사용하는 것은 아니다.

데이터베이스 쿼리 옵티마이저(Query Optimizer)는 쿼리를 실행하기 전에, 해당 쿼리를 어떤 방식으로 최적화해서 실행할지 계획한다. 즉, 데이터의 분포나 쿼리의 형태에 따라 인덱스를 사용하는 것 테이블 전체를 스캔하는 것 중 어느 쪽이 더 효율적인지 비용을 계산하여 최적의 실행 계획을 선택한다. 이때 옵티마이저의 결정 기준이 바로 '손익분기점'이다.

 

손익분기점은 인덱스의 비용이 풀 테이블 스캔의 비용보다 높아지는 지점을 뜻한다.

  • 인덱스 비용 = 인덱스 탐색 비용 + 찾은 주소로 원본 테이블에 접근하는 비용 (랜덤 I/O)
  • 풀 테이블 스캔 비용 = 테이블 전체를 순차적으로 읽는 비용 (순차 I/O)

 

👉 일반적으로 조회하려는 데이터의 양이 테이블 전체의 약 20~25%를 초과하면,

인덱스를 통해 디스크의 여러 위치를 오가는 랜덤 I/O (Random I/O) 비용이

테이블 전체를 순서대로 읽는 순차 I/O (Sequential I/O) 비용보다 비싸다고 판단하여

인덱스 사용을 포기하고 풀 테이블 스캔을 선택할 수 있다.

 


🧩 EXPLAIN: 옵티마이저의 실행 계획 확인

 

EXPLAIN옵티마이저가 쿼리를 어떻게 실행할지,  실행 계획(Execution Plan)을 확인하는 명령어다.

이를 통해 쿼리가 인덱스를 사용하는지 확인할 수 있으며, 쿼리 최적화의 출발점이 된다.

-- 기존 쿼리 앞에 EXPLAIN만 붙이면 됨
EXPLAIN SELECT ...

 

EXPLAIN 결과 컬럼에서 눈여겨봐야 할 부분은 다음과 같다.

  • type: 조인 방식 / 접근 방법 (매우 중요)
    • ALL 풀 테이블 스캔 (반드시 피해야 함) ❌
    • index → 인덱스 전체 스캔
    • ref  인덱스 동등 조건 (=) ✅
    • range  인덱스 범위 검색 (BETWEEN, >, <)
    • const → PK나 UNIQUE 키로 단 하나의 행을 찾는 경우
    • system → 결과가 단 한 행인 경우
    • eq_ref → PK/FK 기반 조인
  • possible_keys: 옵티마이저가 고려할 수 있는 인덱스 목록
  • key: 실제 선택된 인덱스 (풀 테이블 스캔의 경우 NULL)
  • ref: 인덱스 비교에 사용된 값/컬럼
  • rows: 옵티마이저가 예측한 읽을 행 수 (추정치)
  • filtered: 조건 적용 후 남는 비율(%) → rows * filtered/100 = 최종 예상 행 수
  • Extra: 추가 정보
    • Using filesort: ORDER BY에서 인덱스 미사용 별도 정렬 발생 (반드시 피해야 함) ❌
    • Using where: 인덱스에서 WHERE 필터링 수행
    • Using index condition: 인덱스를 활용해 필터링했지만, 추가 데이터를 찾기 위해 원본 테이블 접근 (랜덤 I/O)
    • Using index: 인덱스에 쿼리에 필요한 모든 컬럼이 포함되어 있어, 원본 테이블 접근 없음 (커버링 인덱스)

 


📜 인덱스 활용 시나리오

 

인덱스는 다음 세 가지 상황에서 사용된다.

  1. 동등 비교 (=)
  2. 범위 검색 (BETWEEN, >, <, LIKE 등)
  3. 정렬 (ORDER BY)

 

1. 동등 비교 (=)

-- products 테이블의 name 컬럼에 대해 인덱스 생성
CREATE INDEX idx_products_name ON products (name);

-- 상품 이름 동등 조회
EXPLAIN SELECT * FROM products WHERE name = '맥북 프로';
  • type: ref인덱스 동등 비교
  • key: idx_products_name → 선택된 인덱스
  • rows: 1 → 인덱스를 통해 원본 데이터의 위치를 바로 찾음
  • filtered: 100.00 → 인덱스를 통해 찾은 1개의 행을 100% 선택
  • Extra: NULL → 인덱스에서 이미 모든 검색 조건이 충족되어 별도 필터링이 필요 없음
인덱스의 정렬된 구조 덕분에 탐색 시작점을 빠르게 찾을 수 있다.

 

 

2. 범위 검색 (BETWEEN, >, <, LIKE 등)

-- products 테이블의 price 컬럼에 대해 인덱스 생성
CREATE INDEX idx_products_price ON products (price);

-- 상품 가격 범위 조회
EXPLAIN SELECT * FROM products WHERE price BETWEEN 5000 AND 10000;
  • type: range 인덱스 범위 검색 (인덱스에서 price가 5000 이상인 지점부터 10000 초과 지점까지만 읽음)
  • key: idx_products_price → 선택된 인덱스
  • rows: 5 → 인덱스를 통해 조건에 맞는 데이터만 읽음
  • filtered: 100.00  인덱스를 통해 찾은 행을 100% 선택
  • Extra: Using index condition 인덱스를 활용해 검색 조건을 필터링하여, 조건에 맞는 데이터만 접근함
인덱스의 정렬된 구조 덕분에 탐색 시작점을 빠르게 찾아서 연속된 데이터 블록을 읽을 수 있다.

 

 

⚠️ 주의: LIKE 절에서 인덱스가 동작하려면 % 와일드카드가 반드시 검색어 뒤쪽에 위치해야 한다. ('검색어%')

이는 B-Tree가 '왼쪽 기준'으로 정렬되어 있기 때문이다.

  • ✅ LIKE '검색어%': 인덱스에서 '검색어'로 시작하는 첫 위치를 빠르게 찾은 후, 그 지점부터 순차적으로 스캔하면 된다.
  • ❌ LIKE '%검색어': 시작 문자를 알 수 없으므로 인덱스의 정렬 이점을 전혀 활용할 수 없어 풀 테이블 스캔이 발생한다.

 

내용 중간에 포함된 단어를 검색해야 할 경우, LIKE '%검색어%'는 데이터가 많아지면 서비스 장애의 원인이 될 수 있다.

이런 요구사항에는 데이터베이스가 제공하는 전문 검색(Full-Text Search) 기능을 사용하거나, Elasticsearch와 같은 외부 검색 엔진을 도입하는 것이 올바른 해결책이다.

 

 

3. 정렬 (ORDER BY)

-- 인덱스 없이 정렬하는 경우
EXPLAIN SELECT * FROM products
ORDER BY price;
  • type: ALL → 풀 테이블 스캔
  • Extra: Using filesort  추가 정렬 작업 수행 (비용 증가) ❌

⚠️ filesort는 매우 비용이 높은 작업이기 때문에, 인덱스를 사용하여 최대한 피해야 한다.

-- 이상적인 경우: 하나의 인덱스로 검색과 정렬을 모두 해결
CREATE INDEX idx_products_price ON products (price);

-- WHERE 절의 조건과 ORDER BY 절의 정렬 기준이 같음
EXPLAIN SELECT * FROM products
WHERE price BETWEEN 5000 AND 10000
ORDER BY price;
  • type: range
  • Extra: Using index conditionfilesort가 없음 (별도의 정렬 작업이 필요 없음) ✅
idx_products_price 인덱스는 이미 price 순서로 정렬되어 있다.
따라서 WHERE 조건에 맞는 데이터를 찾기 위해 인덱스를 스캔하는 것만으로도, 자연스럽게 price 순서로 정렬된 결과를 얻을 수 있다.

 

 

⚠️ 만약 ORDER BY 절을 오름차순(ASC)이 아니라 내림차순(DESC)으로 설정한 경우,

옵티마이저는 인덱스를 반대 방향으로 읽는 역방향 스캔(Backward Index Scan)을 한다.

CREATE INDEX idx_products_price ON products (price);

EXPLAIN SELECT * FROM products
WHERE price BETWEEN 5000 AND 10000
ORDER BY price DESC; -- 내림차순의 경우
  • type: range
  • Extra: Using index condition; Backward index scan 인덱스를 역순으로 스캔함

 

⚠️ 역방향 스캔보다 정방향 스캔이 미세하게 더 빠르다. 참고로 이는 컴퓨터 하드웨어의 미리 읽기(prefetching) 기능 때문으로, 따라서 ORDER BY가 특정 컬럼의 내림차순으로 자주 사용된다면, 해당 컬럼에 대해 내림차순 인덱스를 생성하는 것이 좋다.

-- 내림차순 인덱스 생성
CREATE INDEX idx_products_price ON products (price DESC);

EXPLAIN SELECT * FROM products
WHERE price BETWEEN 5000 AND 10000
ORDER BY price DESC;
  • type: range
  • Extra: Using index condition → 정렬 방향과 일치하는 인덱스를 사용했으므로 정방향 스캔함
단일 컬럼 인덱스에서는 역방향 스캔내림차순 인덱스 간의 성능 차이가 크지 않을 수 있지만, 여러 컬럼에 대해 서로 다른 정렬 순서가 필요한 복잡한 쿼리에서 다중 컬럼 인덱스(복합 인덱스)를 사용하면 내림차순 인덱스의 진가가 발휘된다.

 


✅ 커버링 인덱스 (Covering Index): 최고의 성능 최적화

 

커버링 인덱스 쿼리에 필요한 모든 컬럼을 포함하고 있는 인덱스를 의미한다.

 

일반적으로 인덱스를 사용하면 다음 2단계 과정을 거친다.

  1. 인덱스에서 데이터의 위치를 찾는다.
  2. 그 위치를 기반으로 원본 테이블에 접근하여 최종 데이터를 가져온다. (랜덤 I/O)

 

하지만 커버링 인덱스를 사용하면, 원본 테이블에 접근할 필요 없이, 오직 인덱스만 읽어서 쿼리를 처리할 수 있다.

 

👉 즉, 커버링 인덱스랜덤 I/O 과정을 생략할 수 있게 해주는 강력한 최적화 기법이다.

-- 1. 일반 인덱스 사용 (컬럼: price)
CREATE INDEX idx_products_price ON products (price);

EXPLAIN SELECT price, name -- 인덱스에 없는 name 컬럼 조회
FROM products
WHERE price BETWEEN 5000 AND 10000;
  • Extra: Using index condition
    • WHERE 절(필터링)에서는 인덱스를 사용했지만, 최종 데이터를 가져오기 위해 원본 테이블에 랜덤 I/O 접근 필요
-- 2. 커버링 인덱스 사용 (컬럼: price, name)
CREATE INDEX idx_products_price ON products (price, name);

EXPLAIN SELECT price, name -- 인덱스에 두 컬럼 모두 존재
FROM products
WHERE price BETWEEN 5000 AND 10000;
  • Extra: Using where; Using index
    • Using where: 인덱스 내에서 WHERE 절을 통해 데이터를 필터링함
    • Using index: 원본 테이블 접근 없이 인덱스만 사용해서 쿼리를 처리함

 

⚠️ 컬럼이 여러 개인 복합 인덱스에서 '컬럼의 순서'는 매우 중요하다. (복합 인덱스에서 자세히 알아본다.)

 

 

✅ 커버링 인덱스의 장점

  • SELECT 쿼리 성능 향상: 원본 테이블 접근을 위한 랜덤 I/O를 제거하여 조회 성능을 압도적으로 개선한다.
  • COUNT 쿼리 최적화: 테이블 전체가 아닌, 상대적으로 크기가 작은 인덱스만 스캔하여 결과를 빠르게 반환할 수 있다.

 

❌ 커버링 인덱스의 단점

  • 저장 공간 증가: 원본 테이블과 별도로 인덱스를 생성하기 때문에, 인덱스의 컬럼이 늘어날수록 저장 공간이 증가한다.
  • 쓰기 성능 저하: INSERT, UPDATE, DELETE 작업 시 원본 테이블뿐만 아니라 인덱스도 함께 수정해야 하므로 쓰기 성능이 저하된다.

 

따라서 커버링 인덱스는 만능이 아니며, 읽기 성능과 쓰기 성능의 트레이드오프(Trade-off)를 신중하게 고려하여 사용해야 한다.

  • 쓰기 작업은 드물고, 읽기 작업은 매우 빈번한 테이블에 적용
  • SELECT 절에서 조회하는 컬럼의 수가 적을수록 유리 (인덱스의 컬럼이 많아지면 일반 테이블과 다를 바가 없다.)
  • 성능 저하가 발생하는 특정 쿼리를 튜닝하기 위한 '비장의 무기'로 사용

 


🧩 복합 인덱스 (Composite Index)

 

복합 인덱스두 개 이상의 컬럼을 묶어 하나의 인덱스로 만드는 것이다.

-- 복합 인덱스 (컬럼: category, price)
CREATE INDEX idx_products_category_price ON products (category, price);

 

복합 인덱스는 다중 조건 쿼리의 성능을 최적화하는 데 필수적이지만, '컬럼의 순서'가 성능에 결정적인 영향을 미친다.

복합 인덱스를 효과적으로 사용하기 위해서는 다음 세 가지 원칙을 반드시 기억해야 한다.

 

⚠️ 복합 인덱스의 3가지 대원칙

 

1. 왼쪽 접두어 규칙: 인덱스는 순서대로 사용하라!

  • 인덱스가 (A, B, C) 순서로 생성되었다면, WHERE 절에서 A 조건 없이 B C으로는 인덱스를 효율적으로 사용할 수 없다.
-- 1. 왼쪽 접두어 규칙: WHERE 절에 첫 번째 컬럼을 빼지 마라
EXPLAIN SELECT * FROM products
WHERE category = '전자기기'; -- 첫 번째 컬럼만으로 필터링 ✅
-- 이미 카테고리순으로 정렬되어 있음

EXPLAIN SELECT * FROM products
WHERE category = '전자기기' AND price = 100000; -- 첫 번째, 두 번째 컬럼으로 필터링 ✅
-- 각 카테고리는 이미 가격순으로 정렬되어 있음

EXPLAIN SELECT * FROM products
WHERE price = 100000; -- 두 번째 컬럼만으로 필터링 ❌
-- price = 100000인 상품은 여러 카테고리에 존재할 수 있으므로, 풀 테이블 스캔 발생

 

 

2. 동등(=) 조건은 앞으로, 범위(<, >) 조건은 뒤로!

  • 복합 인덱스의 특정 컬럼에 범위 조건이 사용되면, 그 뒤에 오는 컬럼은 인덱스의 '정렬 효과'를 제대로 활용할 수 없다.
  • 따라서 변별력이 높은 등호(=)나 IN 조건을 사용하는 컬럼을 인덱스 앞쪽에 배치하고,
  • 범위 검색마지막에 한 번만 사용하는 것이 성능에 유리하다.
-- 2. 동등 조건 먼저, 범위 조건은 나중에
EXPLAIN SELECT * FROM products
WHERE category = '전자기기' AND price > 100000;
-- 첫 번째 컬럼은 동등 비교, 두 번째 컬럼은 범위 비교 ✅

EXPLAIN SELECT * FROM products
WHERE category >= '패션' AND price = 100000;
-- 첫 번째 컬럼에서 범위 비교를 했으므로, 두 번째 컬럼에서는 인덱스 활용 불가 ❌
-- price 컬럼은 데이터를 효율적으로 찾는 데 사용되지 못하고 추가적인 필터링에만 사용됨 (filtered: 10.00)

EXPLAIN SELECT * FROM products
WHERE category IN ('패션', '헬스/뷰티') AND price = 100000;
-- IN 조건은 동등 비교로 취급 ✅ (filtered: 100.00)
옵티마이저는 IN 조건을 범위로 취급하지 않고, 여러 개의 동등 비교(=) 조건의 묶음으로 인식한다.
실무에서는 범위가 한정적인 컬럼에 이 IN 트릭을 자주 사용한다.

 

 

3. 정렬(ORDER BY)할 때도 인덱스 순서대로 하라!

  • ORDER BY 절이 인덱스 컬럼 순서와 일치하면, 데이터베이스는 이미 정렬된 인덱스를 순서대로 읽기만 하면 된다.
  • 즉, 비용이 큰 정렬 작업(filesort)을 생략할 수 있다.
-- 3. 정렬 순서를 인덱스 순서와 맞춰라
EXPLAIN SELECT * FROM products
WHERE category = '전자기기' AND price > 100000
ORDER BY category, price;
-- 인덱스 순서대로 정렬 ✅

EXPLAIN SELECT * FROM products
WHERE category = '전자기기' AND price > 100000
ORDER BY price;
-- 이미 첫 번째 컬럼으로 필터링되었으므로, 두 번째 컬럼만으로도 정렬 가능 ✅

EXPLAIN SELECT * FROM products
WHERE category = '전자기기' AND price > 100000
ORDER BY name; -- 인덱스에 없는 컬럼
-- name 컬럼은 인덱스와 무관하므로, 필터링 후 별도의 정렬 작업(filesort)이 발생 ❌

 


✅ 인덱스 설계 전략

 

데이터베이스의 인덱스를 잘못 설계할 경우 전체 시스템의 성능을 오히려 떨어뜨릴 수 있다.

따라서 인덱스를 설계할 때의 중요한 점은 어디에 인덱스를 만들어야 하는지 아는 것이다.

 

👉 "카디널리티(Cardinality)가 높은 컬럼에 생성"

 

카디널리티가 높다는 것은 데이터의 고유성(Uniqueness)이 높다는 뜻이다.

즉, 중복이 적은 컬럼에 인덱스를 생성해야 효과적이다.

 

👉 주요 생성 대상:

  • WHERE 절에서 자주 사용되는 컬럼
  • JOIN의 연결고리가 되는 컬럼 (특히 외래 키) → 조인 시 인덱스가 없다면 테이블 전체를 스캔하여 연결고리(FK)를 찾음
  • ORDER BY 절에서 자주 사용되는 컬럼 → 정렬 시 인덱스가 없다면 전체 데이터를 메모리에 올려서 정렬하기 때문

 


❌ 인덱스의 단점과 비용

 

인덱스는 '공짜'가 아니며, 다음과 같은 비용을 수반한다.

  • 저장 공간: 인덱스는 원본 테이블과는 별개로, 물리적인 파일로 디스크에 저장된다. (일반적으로 원본 테이블 크기의 10%)
  • 쓰기 성능 저하: INSERT, UPDATE, DELETE 작업 시, 원본 테이블 데이터뿐만 아니라 관련된 모든 인덱스도 함께 수정해야 한다.

 


⚠️ 중요: 인덱스 컬럼은 가공하면 안된다.

 

WHERE 절에서 인덱스가 적용된 컬럼을 함수로 감싸거나 계산하는 등 가공하면 인덱스가 적용되지 않는다.

인덱스는 가공되지 않은 원본 값을 기준으로 만들어지기 때문이다.

EXPLAIN SELECT * FROM products
WHERE SUBSTRING(name, 1, 5) = '게이밍';

EXPLAIN SELECT * FROM products
WHERE price * 10 = 1000000;

-- 인덱스 컬럼이 가공되면 인덱스를 활용하지 못하고 테이블을 전체 스캔함 ❌

 

따라서 인덱스 컬럼을 가공하지 말고, LIKE 연산자 등을 통해 조건 자체를 변경하거나, 원본 상태 그대로 사용해야 한다.

'DB' 카테고리의 다른 글

[DB] 트랜잭션 (Transaction) – ACID 속성, 트랜잭션 격리 수준  (0) 2025.09.22
[DB] 데이터 무결성 (Data Integrity), 제약 조건 (Constraints)  (0) 2025.09.21
[DB] 뷰 (VIEW)  (1) 2025.09.19
[DB] CASE 문  (0) 2025.09.19
[DB] UNION  (0) 2025.09.19