RyanNerd
라덕'Story
RyanNerd
  • 분류 전체보기 (117)
    • Study Note (80)
      • Python (3)
      • R (1)
      • Airflow (7)
      • 통계 (26)
      • Machine Learning (16)
      • Streamlit (9)
      • Django (1)
      • Deep Learning (12)
      • dbt (2)
      • SQL (3)
    • 빅데이터분석기사 (1)
      • 필기 (1)
    • Programmers (28)
      • Python (13)
      • SQL (15)
    • Project (3)
      • Django (3)
    • Mac (2)

블로그 메뉴

  • NaverBlog
  • 홈

최근 글

전체 방문자
오늘
어제
hELLO · Designed By 정상우.
RyanNerd

라덕'Story

Study Note/SQL

SQL 인덱스 가이드: "내 쿼리는 왜 느릴까?"

2026. 2. 24. 16:04

의료 데이터는 보통 수백만 건의 처방, 검사 결과가 쌓이는 시계열 데이터입니다. 인덱스 없이 쿼리를 날리는 것은 마치 수만 권의 의학 서적이 무질서하게 쌓인 도서관에서 특정 환자의 기록을 한 장씩 넘기며 찾는 것과 같습니다.

오늘은 성능 최적화의 핵심인 인덱스의 3가지 개념을 완벽히 정리해 보겠습니다.

 

1. Clustered Index: "책장을 정리하는 기준"

Clustered Index는 테이블 자체를 특정 순서로 물리적으로 재배열하는 것입니다.

  • 비유: 환자 차트를 '환자 번호(ID) 순'으로 정렬해서 캐비닛에 꽂아두는 것과 같습니다.
  • 특징:
    • 테이블당 단 하나만 만들 수 있습니다. (책을 꽂는 방식은 하나뿐이니까요!)
    • 데이터 자체가 인덱스 순서로 정렬되어 저장됩니다.
  • 왜 빠를까? id = 1인 데이터를 찾을 때, DB는 "1번 환자 데이터는 여기서부터 여기까지네!" 하고 해당 구간으로 바로 점프(Seek)해서 연속된 데이터를 읽기만 하면 됩니다.

2. Non-Clustered Index: "책 뒤편의 찾아보기(색인)"

데이터 본문은 그대로 두고, 별도의 색인 페이지를 만드는 방식입니다.

  • 비유: 전공 서적 맨 뒤에 있는 '키워드 찾아보기' 페이지입니다.
    • 예: "Creatinine 수치: 125페이지, 340페이지..."
  • 특징:
    • 여러 개를 만들 수 있습니다.
    • 인덱스 페이지에는 (정렬된 키워드 + 실제 데이터가 있는 주소)가 적혀 있습니다.
  • 단점 (The Lookup): 색인에서 위치를 찾았더라도, 실제 cr_mgdl 값을 확인하려면 다시 본문 페이지(주소)로 이동해야 합니다. 데이터가 많아지면 이 '왔다 갔다' 하는 과정(Key Lookup) 때문에 성능이 급격히 떨어집니다.

3. INCLUDE 인덱스: "찾아보기에 요약 내용 적어두기"

Non-Clustered Index의 단점인 '본문 다시 가기(Lookup)'를 해결하는 영리한 방법입니다.

  • 비유: 찾아보기 페이지에 단어만 적는 게 아니라, 옆에 간단한 뜻이나 수치도 같이 적어두는 것입니다.
    • 예: "환자 1번, 1월 1일 기록 (수치: 1.0) -> 125페이지"
  • 작동 원리:
CREATE INDEX IX_Cr_Data 
ON #cr_event(id, cr_dt) 
INCLUDE (cr_mgdl);

위처럼 만들면 인덱스 페이지 안에 cr_mgdl 값이 포함됩니다. DB는 본문 페이지로 이동할 필요 없이 인덱스 페이지만 보고도 MIN(cr_mgdl) 계산을 끝낼 수 있습니다. 이를 Covering Index(커버링 인덱스)라고 부릅니다.

 

실전 사례: AKI(급성 신손상) Cr1 계산 쿼리

"48시간 내 최저 Creatinine 찾기" 쿼리를 예로 들어볼까요?

-- 핵심 연산 부분
SELECT MIN(e2.cr_mgdl)
FROM #cr_event e2
WHERE e2.id = e.id
  AND e2.cr_dt >= DATEADD(HOUR, -48, e.cr_dt)
  AND e2.cr_dt < e.cr_dt

 

인덱스가 없을 때 (Full Table Scan)

DB는 수백만 행을 처음부터 끝까지 다 읽으며 "이게 1번 환자인가? 시간은 맞나?"를 무한 반복합니다. 서버가 비명을 지르는 단계입니다.

(id, cr_dt) 인덱스만 있을 때

1번 환자의 시간 범위까지는 빠르게 찾아가지만, 최저값을 찾기 위해 매번 실제 테이블의 주소를 찾아가서 cr_mgdl 값을 하나하나 꺼내와야 합니다. (Key Lookup 발생)

(id, cr_dt) + INCLUDE (cr_mgdl) 일 때 (Best!)

1번 환자의 시간 범위로 점프한 뒤, 그 자리에 적혀 있는 cr_mgdl 값들을 쭉 읽어서 바로 최저값을 구합니다. 본문 테이블에는 근처에도 가지 않습니다. 속도가 수십 배 이상 빨라집니다.

 

인덱스 전략 3줄 요약

  1. 시계열의 정석: 환자 ID와 발생 시간(id, cr_dt) 조합은 거의 항상 Clustered Index 혹은 인덱스의 선두 컬럼으로 고려
  2. Lookup을 막아라: 특정 수치(cr_mgdl, blood_pressure 등)를 자주 조회하거나 집계(MIN, MAX)한다면 INCLUDE에 포함시켜 Lookup 비용을 없애세요.
  3. 과유불급: 인덱스가 많아지면 조회는 빠르지만, 데이터를 넣을 때(Insert)마다 인덱스도 업데이트해야 하므로 쓰기 속도가 느려집니다. 꼭 필요한 컬럼에만 전략적으로 만드세요!

'Study Note > SQL' 카테고리의 다른 글

SQL Window Function: Window Frame의 이해  (0) 2026.04.16
APPLY 연산자란?  (0) 2026.04.15
    'Study Note/SQL' 카테고리의 다른 글
    • SQL Window Function: Window Frame의 이해
    • APPLY 연산자란?
    RyanNerd
    RyanNerd
    라이언 덕후의 일상 스토리~

    티스토리툴바