인덱스 튜닝

이강현·2025년 6월 1일

SQL 튜닝

목록 보기
3/3

테이블 엑세스 최소화

테이블 랜덤 엑세스

인덱스에 대한 맹신 또는 섣부른 자신감

  • Partition Pruning: 조건절에 해당하는 파티션만 읽는 기능
  • ❓ 인덱스를 이용해도 조건절에 맞는 데이터로 바로 찾아가는데?
    Partition Pruning 은 왜 필요할까? 인덱스로 검색하는데 왜 느릴까?

인덱스 ROWID 는 물리적 주소? 논리적 주소?

  • 인덱스 ROWID 는 데이터파일 번호, 오브젝트 번호, 블록 번호 같은 물리적 요소로 이루어짐
  • ✅ 그러나 ROWID 는 논리적 주소에 가깝다.
  • 물리적 주소라고 하면 쉽게 이해하는 C 언어의 pointer 처럼 메모리상의 주소를 가진것이 아님
  • 그 물리적인 주소를 찾아기기 위한 논리적인 정보가 담겨있음
  • ⚠️ 테이블 레코드와 물리적으로 직접 연결된 구조가 아니다

메인 메모리 DB 와 비교

  • 메인 메모리 DB: 데이터를 메모리에 모두 로드함. 메모리 I/O 만 있음
  • 데이터가 메모리의 고정된 주소에 있기에, 인덱스는 메모리의 물리적 주소를 가질 수 있음
  • ORACLE DB: 테이블 블록이 버퍼캐시에서 밀려났다 다시 캐싱되며 수시로 주소가 바뀜
  • 메모리 주소 정보(포인터)가 아닌 디스크 주소 정보를 이용해 해시 알고리즘으로 버퍼 블록을 찾아감

I/O 메커니즘 복습

  • 인덱스 탐색 => ROWID = DBA + 로우 번호
  • DBA(Data Block Address)
  • DBA 해시함수에 대입
  • 해시 체인 탐색
    • 찾았다. 버퍼 헤더가 가지고 있는 포인터로 버퍼 블록 접근
    • 못 찾았다. DBA 를 사용해서 디스크 접근
  • 테이블 Full Scan
  • 익스텐트 맵에서 읽을 블록들의 DBA 정보 가져옴
  • 해시함수에 대입, 체인 탐색
  • 찾았다 / 못 찾았다

인덱스 클러스터링 팩터

  • Clustering Factor: 특정 컬럼을 기준으로 같은 값을 갖는 데이터가 서로 모여있는 정도
  • CF 가 좋은 컬럼에 대한 인덱스는 검색 효율이 매우 좋다
  • 버퍼 Pinning: 래치 획득, 체인 스캔 과정을 거쳐 어렵게 얻은 테이블 블록에 대한 포인터,
    DBA 와 포인터의 연결을 바로 해제하지 않고 유지함
  • 다음 인덱스 레코드를 읽었는데 직전과 같은 블록을 가리킨다면,
    유지했던 포인터를 바로 사용 가능

인덱스 손익분기점

  • 인덱스를 이용하면 전체중에 얼마나 추출하느냐에 따라 성능이 달라짐
  • 테이블 랜덤 액세스 때문에 추출 건수가 많을수록 느려짐
Table Full ScanIndex ROWID 를 이용한 테이블 액세스
시퀀셜 엑세스랜덤 액세스
Multiblock I/OSingle Block I/O
  • CF 가 좋으면 손익분기점이 상승, 나쁘면 감소
  • 전체 테이블의 절대적 크기도 영향이 있음
  • 데이터 양이 크면 BCHR 감소, CF 감소
  • 1000만건 정도의 테이블이고, 메모리가 막대하게 큰게 아니면
    손익분기점이 의미가 없고, Table Full Scan 이 압도적으로 유리

온라인 프로그램 튜닝 VS 배치 프로그램 튜닝

  • 온라인 프로그램(OLTP)
    • 소량의 데이터를 읽고 갱신
    • 인덱스 활용이 중요
    • NL 조인, 부분범위 처리
  • 배치 프로그램(OLAP)
    • 대량의 데이터를 읽고 갱신
    • 전체를 빠르게 처리하는 것이 목표
    • Full Scan, Hash 조인
    • 파티션 활용 전략, 병렬 처리가 중요

인덱스 컬럼 추가

  • 테이블 액세스를 최소화 하기 위한 가장 기본적인 방법
  • 전체 블록 I/O 에서 테이블 랜덤 액세스 비중이 높다면 효과적인 선택
  • 즉, 테이블 액세스 단계에서 필터 조건에 의해 버려지는 레코드가 많을 때
  • ⚠️ 인덱스를 수정하는 기능은 없음. 기존의 인덱스를 삭제하고 새로 만들기
  • ⚠️ 인덱스를 무작정 많이 만들면 인덱스 관리 비용 증가, DML 성능 저하가 생김

인덱스만 읽고 처리

  • 쿼리에 사용된 컬럼은 모두 인덱스에 추가
  • 테이블 액세스가 발생하지 않음
  • Covered Query: 인덱스만 읽어서 처리하는 쿼리
  • Covered Index: covered query 에 사용한 인덱스

Include 인덱스

  • ⚠️ ORACLE 에 없음, SQL Server 기능
  • 인덱스 키 외에 미리 지정한 컬럼을 리프 레벨에 함께 저장
  • 인덱스의 추가적인 비용 없이 테이블 액세스 없앰

인덱스 구조 테이블

  • IOT(Index-Organized Table)
    • 테이블을 인덱스 구조로 생성함
    • 테이블 블록에 있어야할 데이터를 리프 블록에 모두 저장
  • 정렬되어 있어서 같은 값을 가진 레코드들은 반드시 모여 있음 -> Clustering Factor 좋음
  • 입력과 조회 패턴이 서로 다른 테이블에 유용
    • 조회 쿼리에 사용되는 컬럼을 첫번째 정렬 기준이 되도록 IOT 를 생성
    • 랜덤 액세스가 아니라 시퀀셜 액세스를 써서 블록 I/O 가 줄어든다
CREATE TABLE INDEX_ORG_T(
    A NUMBER, 
    B VARCHAR(10), 
    CONSTRAINT INDEX_ORG_T_PK PRIMARY KEY (A)
    )
ORGANIZATION INDEX; -- 기본값 HEAP 은 생략 가능
  • Heap 구조 VS IOT
    • Heap: Freelist 로부터 할당 받은 블록에 정해진 순서 없이 랜덤 방식 입력
    • IOT: 정렬 상태를 유지하며 입력

클러스터 테이블

  • 클러스터 테이블
    • 인덱스 클러스터 테이블
      • 클러스터 키 값이 같은 레코드를 한 블록에 저장
        (한 블록보다 크면 새로운 블록을 할당해 클러스터 체인으로 연결)
      • IOT 나 클러스터형 인덱스(SQL Server) 와 달리 정렬은 안함
      • 일반 인덱스는 테이블 레코드와 1:1 관계, 클러스터 인덱스는 블록을 가리킴 1:M 관계
      • 랜덤 액세스가 키 값마다 한번. 인덱스를 타고 클러스터에 도착하면 시퀀셜 스캔
    • 해시 클러스터 테이블
      • 인덱스 클러스터 테이블에서 인덱스를 해시 알고리즘으로 바꾼 것
-- 인덱스 클러스터 테이블

-- 클러스터 생성
CREATE CLUSTER C_DEPT# (DEPTNO NUMBER(2)) INDEX;
-- 클러스터 인덱스 정의 (데이터 검색, 저장 위치 탐색 용도)
CREATE INDEX C_DEPT#_IDX ON CLUSTER C_DEPT#;
-- 클러스터 테이블 생성
CREATE TABLE DEPT_CLUSTER (
    DEPTNO NUMBER(2) NOT NULL,
    DNAME VARCHAR2(14) NOT NULL,
    LOC VARCHAR2(13) 
    )
CLUSTER C_DEPT#(DEPTNO);
-- 데이터 삽입
INSERT INTO DEPT_CLUSTER VALUES (10, 'ACCOUNTING', 'NEW YORK');
INSERT INTO DEPT_CLUSTER VALUES (20, 'RESEARCH', 'DALLAS');
INSERT INTO DEPT_CLUSTER VALUES (30, 'SALES', 'CHICAGO');
INSERT INTO DEPT_CLUSTER VALUES (40, 'OPERATIONS', 'BOSTON');
-- 조회 쿼리
SELECT * FROM DEPT_CLUSTER WHERE DEPTNO = 20;
/* 실행 계획
SELECT STATEMENT
- TABLE ACCESS(DEPT_CLUSTER) CLUSTER
- - INDEX(C_DEPT#_IDX) UNIQUE SCAN
- - - ACCESS PREDICATES
- - - - DEPTNO=20
*/
-- 해시 클러스터 테이블

-- 클러스터 생성
CREATE CLUSTER C_DEPT# (DEPTNO NUMBER(2)) HASHKEYS 4;
-- 클러스터 테이블 생성
CREATE TABLE DEPT_CLUSTER (
    DEPTNO NUMBER(2) NOT NULL,
    DNAME VARCHAR2(14) NOT NULL,
    LOC VARCHAR2(13) 
    )
CLUSTER C_DEPT#(DEPTNO);
-- 데이터 삽입
INSERT INTO DEPT_CLUSTER VALUES (10, 'ACCOUNTING', 'NEW YORK');
INSERT INTO DEPT_CLUSTER VALUES (20, 'RESEARCH', 'DALLAS');
INSERT INTO DEPT_CLUSTER VALUES (30, 'SALES', 'CHICAGO');
INSERT INTO DEPT_CLUSTER VALUES (40, 'OPERATIONS', 'BOSTON');
-- 조회 쿼리
SELECT * FROM DEPT_CLUSTER WHERE DEPTNO = 20;
/* 실행 계획
SELECT STATEMENT
- TABLE ACCESS(DEPT_CLUSTER) HASH
- - ACCESS PREDICATES
- - - DEPTNO=20
*/

부분범위 처리 활용

  • 테이블 액세스로 인한 인덱스 손익분기점의 한계를 극복

부분범위 처리

  • DBMS 가 클라이언트에게 데이터를 전송할 때 일정량(Array Size)씩 나누어 전송
  • 아직 전송하지 않은 부분이 많이 남아있어도 서버 프로세스는 추가 Fetch Call 전까지 기다림
    • 데이터를 일정량 전송하고 나면 서버 프로세스는 CPU 를 OS 에 반환하고 대기 큐에서 잠을 잠
    • 다음 Fetch Call 을 받으면 대기 큐에서 나와 그다음 데이터부터 일정량을 읽어 전송하고 다시 잠
  • OLTP 환경에서 대용량 데이터를 빠르게 핸들링
  • 우리가 사용하는 쿼리 툴에서 아무리 큰 테이블을 전체 조회 해도 결과가 바로 나오는 이유
    • 결과에서 스크롤을 내리면 추가 Fetch Call

정렬 조건이 있을 때 부분범위 처리

  • 정렬 조건이 선두 컬럼인 인덱스가 없다면,
    모든 데이터를 다 읽어 정렬을 마치고서야 클라이언트에게 데이터 전송을 시작

Array Size 조정을 통한 Fetch Call 최소화

  • 네트워크를 통해 전송해야 할 데이터량에 따라 Array Size 를 조절할 필요가 있음
    • 파일 다운로드 처럼 모든 데이터를 전송해야 한다면, Array Size 를 키워 Fetch Call 횟수를 줄임
    • 앞쪽 일부 데이터만 Fetch 하다 멈추는 프로그램이면, Array Size 를 줄여 불필요한 데이터 전송을 줄임

OLTP 환경에서 부분범위 처리에 의한 성능개선 원리

  • OLTP 는 일반적으로 소량 데이터를 읽고 갱신하지만, 가끔 대량을 조회할 때가 있음
  • 그러나 사용자가 그 결과 데이터를 모두 확인하지는 않음
  • 특정한 정렬 순서로 상위 일부 데이터만 확인
  • 항상 정렬 상태를 유지하는 인덱스를 이용하면 부분범위 처리 가능

멈출 수 있어야 의미있는 부분범위 처리

  • 앞쪽 일부만 출력하고 멈출 수 있는가가 중요
  • 클라이언트와 DB 사이에 WAS, AP 서버 등이 존재하는 n-Tier 아키텍처에서는
    클라이언트가 특정 DB 커넥션을 독점할 수 없음
  • 단위 작업을 마치고 DB 커넥션을 커넥션 풀에 반환해야 함
  • 그 전에 조회 결과를 클라이언트에게 모두 전송하고 커서를 닫아야함
  • 그러나 Top N Query 를 이용하면 여전히 부분범위 처리가 유효하다

배치 I/O

  • 테이블 블록에 대한 디스크 I/O Call 을 미뤘다가 읽을 블록이 일정량 쌓이면 한번에 처리
  • 블록마다 건건이 I/O Call 을 발생시키는 비효율을 줄임
  • ⚠️ 정렬 순서가 매번 다를 수 있음
    • 인덱스를 써도 배치 I/O 기능이 작동하면 ORDER BY 에의한 SORT 연산을 생략할 수 없음
    • ✅ 인덱스를 믿고 ORDER BY 를 생략하지 말자
      SORT 연산 수행과 생략은 옵티마이저가 결정함

인덱스 스캔 효율화

인덱스 탐색

  • 인덱스 컬럼 기준으로 정렬된 데이터들을 블록 크기로 분할
  • 분할된 리프블록의 첫번째 레코드의 키 값을 부모 루트/브랜치 블록의 엔트리 키 값으로 등록
  • 루트/브랜치 블록의 엔트리 키 값이 가리키는 리프 블록의 레코드들은 크거나 같음이 보장됨
  • ⚠️ 그러나 직전의 리프 블록에도 엔트리 키 값과 같은 레코드가 존재할 수 있음
  • ✅ 리프 블록 수평 탐색의 시작점을 찾기 위해서는
    조건이 가르키는 리프 블록보다 하나 전으로 가야함
  • 인덱스 컬럼의 순서에 따라 조건절의 BETWEEN 이 탐색범위를 줄일 수도, 아닐 수도 있음

인덱스 스캔 효율성

  • 인덱스 선행 컬럼이 조건절에 없거나 '=' 조건이 아니면 인덱스 스캔 과정에서 비효율 발생
  • 정렬 순서가 다르기 때문에, 수평 탐색의 시작 지점과 끝 지검을 정할 수 없다

인덱스 스캔 효율성 측정

-- 통계 확인하는 방법
SET AUTOTRACE ON
SELECT * FROM TABLE_NAME;

Using Autotrace in SQL*Plus

Database Statistic NameDescription (설명)
recursive calls사용자 및 시스템 수준에서 생성된 재귀 호출의 수. 내부적으로 처리되는 Oracle 시스템 SQL 호출 횟수입니다.
db block getsCURRENT 모드로 블록을 요청한 횟수입니다. 주로 데이터 변경 시 사용됩니다.
consistent getsConsistent Read(일관성 읽기) 방식으로 블록을 요청한 횟수입니다. 주로 SELECT 시 사용됩니다.
physical reads디스크에서 실제로 읽은 데이터 블록의 총 수입니다. 직접 읽기와 버퍼 캐시를 통한 읽기를 모두 포함합니다.
redo size생성된 Redo 로그의 총 바이트 수입니다.
bytes sent via SQL*Net to client클라이언트에 전송된 총 바이트 수입니다.
bytes received via SQL*Net from client클라이언트로부터 수신한 총 바이트 수입니다.
SQL*Net roundtrips to/from client클라이언트와 주고받은 Oracle Net 메시지의 총 수입니다.
sorts (memory)메모리에서만 정렬이 완료되어 디스크 I/O가 발생하지 않은 정렬 작업의 수입니다.
sorts (disk)디스크에 적어도 한 번 이상 쓰기가 발생한 정렬 작업의 수입니다.
rows processedSQL 실행 중 처리된 행(row)의 총 수입니다.

액세스 조건과 필터 조건

  • 인덱스 액세스 조건
    • 인덱스 스캔 범위를 결정하는 조건절
    • 스캔 시작점, 스캔 끝점
  • 인덱스 필터 조건
    • 테이블로 액세스할지를 결정하는 조건절
  • 테이블 필터 조건
    • 쿼리 수행 다음 단계로 전달하거나 최종 결과집합에 포함할지를 결정

비교 연산자 종류와 컬럼 순서에 따른 군집성

  • 인덱스 컬럼을 앞에서 부터 순서대로 '=' 로 조회하면 결과는 모여있음
  • 반대로, 중간에 하나라도 누락하거나 '=' 가 아니면 결과가 흩어짐
  • '=' 가 아닌 처음 등장하는 범위 조건을 만족하는 결과 까지는 모여있음

범위검색 조건 맨 처음과 마지막 구간에서의 액세스 조건

  • 범위 조건도 수평 탐색의 시작점과 끝점을 결정하는데 영향을 줌
  • 몇 가지를 제외하면 인덱스 컬럼에 대한 조건절은 작지만 액세스 조건에 영향을 줌
  • 인덱스 액세스 조건에 영향을 주지 않는 경우
    • 좌변 컬럼을 가공한 조건절
    • 왼쪽 '%' 또는 양쪽 '%' 기호를 사용한 LIKE 조건절
    • 같은 컬럼에 대한 조건절이 두 개 이상일 때, 인덱스 액세스 조건으로 선택되지 못한 조건절
    • OR Expansion 또는 INLIST ITERATOR 로 선택되지 못한 OR 또는 IN 조건절

인덱스 선행 컬럼이 등치 조건이 아닐 때 생기는 비효율

  • 인덱스 스캔 효율성은 인덱스 컬럼을 조건절에 모두 등치 조건으로 사용할 때 가장 좋음
  • 모두 테이블 액세스로 이어지기 때문
  • 인덱스 선행 컬럼이 조건절에 없거나 부등호, BETWEEN, LIKE 같은 범위검색 조건이면 인덱스 스캔 비효율

BETWEEN 을 IN-List 로 전환

  • BETWEEN 조건절을 IN-List 조건절로 바꿔 질의 하면 효율적
  • 옵티마이저가 수직 탐색을 여러번 하도록 실행 계획을 세울 수 있다
  • 등치 조건을 사용한 어러개의 질의들에 UNION ALL 을 적용한 것과 같은 효과

BETWEEN 조건을 IN-List 로 전환할 때 주의 사항

  • BETWEEN 조건 때문에 리프 블록을 많이 스캔하는 비효율보다
    IN-List 개수만큼 브랜치 블록을 반복 탐색하는 비효율이 클 수 있음
  • Depth 가 깊을수록 주의

Index Skip scan 활용

  • 선두 컬럼이 BETWEEN 이어서 나머지 검색 조건을 만족하는 데이터들이 서로 멀리 있을 때
  • 하위 컬럼의 Range Scan 이 끝나면 상위 컬럼의 다음 키값의 시작점으로 Skip

IN 조건은 '=' 인가

  • IN 조건은 '=' 가 아니다
  • IN 조건이 IN-List Iterator 방식으로 풀려야 효율적인 상황도 있지만
  • IN 조건이 필터 조건으로 사용되야 효율적인 경우도 있다

NUM_INDEX_KEYS 힌트 활용

  • IN-List 를 액세스 조건 또는 필터 조건으로 유도하는 방법
  • num_index_keys(테이블명, 인덱스명, 액세스 조건으로 사용할 컬럼 수)
-- 고객별가입상품_X1 INDEX(고객번호, 상품ID)
SELECT /*+ num_index_keys(a 고객별가입상품_X1 2) */ *
FROM 고객별가입상품 a
WHERE 고객번호 = 1234
AND 상품ID IN ('NH00037', 'NH00041', 'NH00050');
-- 힌트 없이는 상품ID 조건절이 필터 조건으로 실행 계획이 잡히지만, 
-- NUM_INDEX_KEYS 함수를 통해 강제해서 IN-List Iterator 방식으로 실행계획 잡힘 
  • 인덱스 컬럼을 가공해서 조건절에 사용하면 힌트 없이 제한할 수 있음

BETWEEN 과 LIKE 스캔 범위 비교

  • LIKE 보다 BETWEEN 을 사용하는 게 낫다
  • BETWEEN 쓰면 적어도 손해는 안봄
SELECT * FROM SALES
WHERE SALES_DATE LIKE '2025%';
SELECT * FROM SALES
WHERE SALES_DATE BETWEEN '202501' AND '202512';
  • BETWEEN 을 쓸 때가 항상 더 구체적이고 제한적
  • 인덱스 스캔 범위를 조금이라도 줄일 가능성이 있음

Dynamic SQL

  • 옵션 조건: 조건이 있을 수도 없을 수도 있음
  • 옵션 조건을 처리하기 위해 OR, LIKE/BETWEEN, UNION ALL, NVL/DECODE 등 여러 방법이 있음
  • MyBatis 같은 SQL 매핑 프레임워크에서는 SQL 을 동적으로 구성할 수 있음
  • 따라서 옵션 조건에 '=' 연산자를 사용할 수 있음
  • Dynamic SQL 을 허용하지 않는 시스템이 아니라면 고민하지 말고 옵션 조건을 동적으로 처리하자
  • 힌트 사용에 주의, 액세스 경로가 원하지 않는 방향으로 고정될 수 있음
  • 하드 파싱에 의한 문제가 발생하지 않게 바인드 변수를 잘 활용, 입력 값까지 동적으로 구성할 필요 없음

인덱스 설계

인덱스 설계가 어려운 이유

  • 인덱스는 정렬상태를 유지해야 해서 삽입/삭제할 때 마다 비용 발생
  • 입력할 리프 블록에 공간이 없으면 Index Split 도 발생
  • 인덱스가 여러개라면 하나의 삽입/삭제에도 부담이 커짐
  • DML 성능 저하
  • 개별 쿼리들의 성능을 높이면서 인덱스의 개수는 최소화 해야함 -> 어렵다

가장 중요한 두 가지 선택 기준

  • 스캔 효율성을 고려한 인덱스 생성 기준
  1. 조건절에 항상 사용하거나, 자주 사용하는 컬럼을 선정
  2. '=' 조건으로 자주 조회하는 컬럼을 앞쪽에 두기

스캔 효율성 이외의 판단 기준

  • 기본적인 스캔 효율성 외에도 예외적으로 고려해야할 기준들이 있다
  1. 수행 빈도
  2. 업무상 중요도
  3. 클러스터링 팩터
  4. 데이터량
  5. DML 부하
  6. 저장 공간
  7. 인덱스 관리 비용
  • 이중 수행 빈도가 가장 중요
    • NL 조인 Inner 쪽 인덱스는 '=' 조건 컬럼을 선두에 두는 것이 중요
    • 테이블 액세스 없이 인덱스에서 필터링을 마치는 게 좋음

공식을 초월한 전략적 설계

  • 조건절 패턴중에 최적을 달성해야 할 핵심적인 액세스 경로 한두개를 전략적으로 선택
  • 최소한의 최적의 인덱스를 설계
  • 나머지 액세스 경로는 약간의 비효율이 있더라도 목표 성능을 만족하는 수준으로
  • 왜 이런 설계를 했는가, 전략적 판단 근거
  • 인덱스 개수를 줄이기 위해 범위 조건 컬럼이 인덱스 선두 컬럼이 될 수도 있음

소트 연산을 생략하기 위한 컬럼 추가

  • 인덱스는 정렬 상태를 보장해서 ORDER BY, GROUP BY 를 위한 소트 연산 생략 가능
  • 조건절에 사용하지 않는 컬럼이라도 소트 연산 생략을 위해 인덱스 구성에 포함 가능
  • I/O 를 최소화하면서도 소트 연산을 생략하기 위한 공식
  1. '=' 연산자로 사용한 조건절 컬럼 선정 (IN 조건 안됨)
  2. ORDER BY 절에 기술한 컬럼 추가
  3. '=' 연산자가 아닌 조건절 컬럼은 데이터 분포를 고려해 추가 여부 결정

결함 인덱스 선택도

  • Selectivity(선택도): Selection 연산 결과 레코드 수 / 전체 레코드 수
  • Cardinality: 선택도 * 전체 레코드 수
  • 인덱스 선택도: 인덱스 컬럼을 모두 '=' 로 조회할 때 평균적으로 선택되는 비율
  • 선택도(카디널리티)가 높은 인덱스는 테이블 액세스가 많아 생성할 필요가 없음
-- GROUP BY 와 집계 함수로 선택도/카디널리티를 확인하자
SELECT COUNT(*) NDV, MAX(CNT) MX_CARD, MIN(CNT) MN_CARD, AVG(CNT) AVG_CARD
FROM (
    SELECT 계약ID, 취급지점ID, COUNT(*) CNT
    FROM 계약조직
    WHERE (계약ID IS NOT NULL OR 취급지점ID IS NOT NULL)
    GROUP BY 계약ID, 취급지점ID
);

컬럼 순서 결정 시, 선택도 이슈

  • 선택도와 인덱스 구성 컬럼 순서는 관계가 없음
  • 항상 사용하는 컬럼을 앞쪽에 두고 그중 '=' 조건을 앞쪽에 위치시키는 게 중요
  • 선택도가 낮은 컬럼을 앞쪽에 두려는 노력은 의미 없거나 오히려 손해
  • 인덱스 생성 여부 -> 선택도 고려
  • 컬럼 간 순서 결정 -> 필수 조건 여부, 연산자 형태 고려

중복 인덱스 제거

  • 선두 컬럼을 완전히 포함하고 있는 인덱스는 제거해도 됨
    1. X01: 계약ID + 청약일자
    2. X02: 계약ID + 청약일자 + 보험개시일자
    3. X03: 계약ID + 청약일자 + 보험개시일자 + 보험종료일자
    • X01, X02 는 완전 중복으로 제거해도 됨
profile
백엔드 개발자 지망생입니다.

0개의 댓글