Skip to content

SQL 인덱스 해설

SQL 인덱스 유형, 설계 패턴, 유지 관리에 관한 치트시트.

인덱스는 저장 공간과 쓰기 속도를 희생해 읽기 속도를 얻는다. 구조를 고르고, 행 범위를 좁히고, 블로트를 관리하라.

참고 표 · 31 항목
31 of 31 rows
인덱스 유형
동등성, 범위, 정렬 스캔을 위한 균형 트리. 대부분 데이터베이스의 기본값.=, <, >, BETWEEN, ORDER BY에 사용. 전문 검색에는 쓰지 않는다.
리프 수준이 테이블 자체인 인덱스. 행이 키 순서로 저장된다. 테이블당 하나.InnoDB는 기본 키로 클러스터링한다. 없는 SQL Server 테이블은 힙이다.
키와 기본 테이블을 가리키는 포인터를 담는 별도 구조. 테이블당 여러 개 가능.힙에서는 행 ID를 저장하고, 클러스터형 테이블에서는 클러스터링 키를 저장한다.
동등성 조회 전용 해시 테이블. 상수 시간의 정확한 일치.= 연산에만 사용. 범위와 정렬에는 B-tree가 필요하다. SQL Server 메모리 최적화 해시는 행 수에 가까운 버킷 수가 필요하다.
Generalized Inverted Index. 값을 행에 매핑한다. 배열과 전문 검색용으로 만들어졌다.jsonb, 배열, tsvector, 트라이그램 검색에 사용. B-tree보다 구축이 느리다.
Generalized Search Tree. 비스칼라 타입과 겹침 쿼리로 확장 가능.기하 범위, kNN, 배제 제약에 사용. pg_trgm은 LIKE를 빠르게 한다.
공간 분할 GiST. 라딕스 또는 쿼드 트리로 데이터를 겹치지 않는 영역으로 나눈다.점, IP 접두사, 접두사 일치에 사용. 겹침 쿼리에는 부적합. GiST를 쓸 것.
Block Range Index. 블록 범위별로 min/max를 저장한다. 공간을 거의 안 쓴다.크고 물리적으로 정렬된 테이블에 사용. 구축은 저렴, 선택도는 약하다.
행 대신 열 세그먼트로 저장되는 인덱스. 높은 압축률, 배치 실행.행이 많고 열이 적은 분석 스캔에 사용. 단일 행 조회는 느리다.
MySQL과 SQL Server의 전용 역방향 단어 인덱스. 텍스트 열마다 구축된다.MATCH AGAINST 또는 CONTAINS를 사용. Postgres 전문 검색은 대신 tsvector에 GIN을 쓴다.
고유 값마다 하나의 비트맵을 저장하는 Oracle 인덱스. 각 비트가 한 행을 표시한다.읽기 중심 웨어하우스의 저카디널리티 열에 사용. 동시 쓰기는 비트맵별로 직렬화된다.
DESC로 선언된 인덱스 열. 해당 열의 키는 역순으로 저장된다.(a ASC, b DESC) 같은 혼합 정렬에 필요. 단순 DESC 정렬은 일반 인덱스를 역방향으로 읽어도 된다.
옵티마이저가 무시하는 MySQL 8 인덱스. 쓰기 때마다 계속 유지된다.삭제 전에 인덱스를 숨겨 영향을 시험하라. 재구축 없이 다시 표시할 수 있다.
설계
여러 열로 구성된 인덱스. 선행 열이 쿼리 필터 순서와 일치해야 한다.동등성 열을 먼저, 범위 열을 나중에. 단일 인덱스 두 개는 복합 인덱스 하나를 대체할 수 없다.
WHERE 술어 위의 인덱스. SQL Server는 필터된 인덱스라고 부른다. 일치하는 행만 덮는다.active='t' 행만 인덱싱하라. 크기와 쓰기 비용이 절반이 된다.
키가 아닌 열을 추가한 B-tree. 인덱스 전용 스캔이 힙 조회를 피한다.자주 가져오는 열을 INCLUDE로 추가하라. 인덱스의 정렬 가능성을 유지한다.
중복 값을 금지하는 인덱스. 충돌하는 삽입을 거부한다.자연 키와 일대일 제약에 사용. UNIQUE NULL은 NULL을 여러 개 허용한다.
열 자체가 아니라 열의 함수 위에 구축된 인덱스.lower(email) 또는 date_trunc('day', created_at)을 인덱싱하라. 쿼리는 정확히 같은 표현식을 반복해야 한다.
연산자 클래스. 인덱스가 한 열의 타입을 어떻게 비교하고 저장하는지 정의한다.대소문자 접기 없는 LIKE 'abc%'에는 varchar_pattern_ops를 설정. 기본값은 동등성과 범위에 적합하다.
인덱스 페이지만으로 쿼리에 답하는 플랜. 힙은 절대 읽히지 않는다.가져오는 모든 열이 인덱스 또는 INCLUDE 목록에 있어야 한다. Vacuum이 가시성 맵을 최신으로 유지한다.
외래 키 열은 참조 테이블에 자동 인덱스를 받지 못한다.조인이나 삭제에 쓰는 FK 열은 모두 인덱싱하라. 없으면 계단식 삭제가 자식 테이블을 스캔한다.
유지 관리
플랜 검사 도구. 순차 스캔, 인덱스 선택, 비용 추정치를 보여 준다.EXPLAIN (ANALYZE, BUFFERS)를 사용하라. 1,000만 행 테이블의 Seq Scan은 인덱스 부재를 뜻한다.
테이블 열을 표본화해 플래너 통계를 만든다. 히스토그램이 인덱스 선택을 좌우한다.대량 적재 후 실행하라. 오래된 통계는 인덱스된 열에서 seq scan을 유발한다.
인덱스마다 쓰기 오버헤드가 는다. 삽입, 갱신, 삭제는 인덱스별로 비용을 치른다.사용하지 않는 인덱스를 삭제하라. 사용량은 pg_stat_user_indexes로 추적한다.
인덱스별 사용량을 보여 주는 Postgres 뷰. 통계 리셋 이후의 스캔 수와 읽은 튜플 수를 센다.idx_scan = 0으로 걸러 미사용 인덱스를 찾으라. 카운터는 pg_stat_reset()으로 리셋한다.
읽기와 쓰기를 막지 않고 인덱스를 구축한다. 테이블 스캔 두 번이 필요하다.트랜잭션 안에서는 실행할 수 없다. 실패한 구축은 INVALID 인덱스를 남기니 삭제하라.
인덱스를 제거하고 저장 공간을 해제한다. 기본적으로 배타 잠금을 건다.트래픽 많은 테이블에서는 DROP INDEX CONCURRENTLY를 사용하라. 제거 전에 사용 통계를 확인하라.
부풀어 오른 인덱스를 재구축한다. 죽은 튜플에서 공간을 회수한다.운영 환경에서는 REINDEX CONCURRENTLY를 사용하라. 없으면 잠금이 쓰기를 막는다.
MVCC 테이블과 그 인덱스에 쌓이는 죽은 튜플과 페이지 간극.pgstattuple로 측정하라. 자주 Vacuum하고, 죽은 공간이 우세하면 REINDEX하라.
쓰기 시점에 페이지를 채우는 비율. 여유 공간이 이후 갱신을 흡수한다.갱신이 잦은 테이블에서는 70-90으로 낮춰라. 꽉 찬 페이지는 페이지 분할을 강제한다.
테이블을 인덱스 순서로 물리적으로 다시 쓴다. 일회성 작업.BRIN과 짝지으면 정렬된 블록이 된다. 이후 쓰기에서는 순서가 유지되지 않는다.