Back-end/SQL

[레거시 정규화] DB 인덱싱 하기

moguogu 2026. 2. 28. 20:20

1. 개요

시스템을 운영하면서 요청건수가 쌓이게되어, 단순하게 조회하는 api 부터 시작해서, 요청의 통계등 관리자 사용 api가 점점 느려지는 것이 느껴졌다.

우선순위를 낮게 두어 계속 밀렸던 인덱싱을 적용하도록 결정했다.

✅ 인덱스란 무엇인가?

인덱스는 테이블의 특정 컬럼에 대해 검색 가능한 색인 구조를 만드는 것이다.
일반적으로 B-Tree 기반 인덱스가 기본이며, 정렬된 구조로 되어 있어 WHERE, JOIN, ORDER BY 등에서 전체 테이블 스캔을 막는다.

왜 인덱스가 성능을 올리는가?

  • 전체 테이블을 순회하는 Full Table Scan 보다 빠르게 조회 가능
  • 키를 통한 검색 범위 지정이 빨라짐
  • 데이터가 많아질수록 효과가 더 커짐

다만, insert/update/delete는 오히려 느려질 수 있기때문에 신중하게 적용해야한다

작업하고 있는 시스템의 경우 사실상 delete는 없고 update 또한 드물다

읽기(조회) vs 쓰기(변경) 사이에서 더 무게를 두자면 읽기에 투자하기로 했다

 

2. Index를 만드는 조건

단순하게 생각해서 where 조건에 가장 많이 들어가는 column이 우선순위가 높다

(cardanality 가 높은 컬럼이 인덱스의 조건이 된다)

이번 시스템에 경우에서는 status이다 

해당 요청의 진행상태가 어떤지 조회하는 api에서 빛을 발할것이다

 

예시) 

#단일 인덱스
CREATE INDEX idx_request_status ON request(status);

#복합 인덱스
CREATE INDEX idx_type_status_created ON request(type, status, created_at);

 

검색 조건도 indexing에 유리하지만, join의 key 정렬 조건 등이 추가로 있을 것이다

 

 

3. 실제 적용 예시

앞서 정규화 한 글에서 보면 아래 3개의 테이블이 있다

사용자가 요청한 요청 정보를 보여주는 API에서는 위 테이블들을 join해서 보여줄 것이다 

 

  • request_content
  • request_info
  • request_approval

요청 정보 조회 쿼리 예시)

SELECT
    ri.request_id,
    rc.request_title,
    ra.approver_id,
    ra.approval_status,
    ra.approved_at
FROM request_info ri
JOIN request_content rc
  ON ri.request_id = rc.request_id
LEFT JOIN request_approval ra
  ON ri.request_id = ra.request_id
WHERE ri.request_id = ?;

 

 

앞서 정리한대로 보면 join의 key가 되는 ri.request_id, rc.request_id ,  ra.request_id 가 인덱스가 필요할 것이다 

 

Approval 테이블 인덱스 생성) 

CREATE INDEX idx_approval_request_id ON request_approval (request_id);

 

FK로 설정한 것들에 대해 index를 설정한다

 

 

+ 결과 추가 필요

 

 

4. 고찰 

index 적용 후에는 EXPLAIN으로 분석해야한다