AD JB Toolbox 01

MariaDB 강좌 / 인덱스 만들기: 단일·복합 인덱스와 열 순서

인덱스는 행을 찾고 정렬하는 데 사용할 별도의 자료 구조이며, 복합 인덱스의 열 순서는 어떤 조회에 유용한지를 좌우합니다. 자주 쓰는 조건·정렬·결과 열과 기존 인덱스를 확인한 뒤 추가하고, 읽기 이득뿐 아니라 쓰기·저장 비용도 함께 평가해야 합니다.

인덱스는 데이터 복사와 유지 비용을 갖는 구조

테이블의 모든 행을 찾는 대신 정렬된 키를 이용해 필요한 범위를 좁힐 수 있습니다. 하지만 INSERT·UPDATE·DELETE 시 인덱스도 유지해야 하므로 많을수록 항상 좋은 것은 아닙니다. 사용 빈도·선택되는 행 비율·결합·정렬을 기준으로 후보를 정합니다. 추가한 키의 실제 선택은 EXPLAIN으로 실행 계획 읽고 인덱스 전후 비교하기으로 확인하고, 결과 의미를 보존하는 조건·페이지 변경은 조회 조건·정렬·페이지 나누기를 개선하기에서 대조합니다.

조회 형태 검토할 키 순서 확인 사항
고객별 최근 주문 customer_id, ordered_at, order_id 동등 조건 뒤 시간 범위·정렬
날짜만으로 전체 주문 ordered_at 등 별도 요구 고객 우선 키와 요구가 다름
고유 상품 코드 UNIQUE(sku) 무결성 조건과 검색 역할 모두
이미 있는 FK 인덱스 SHOW INDEX 확인 같은 선두 열 중복 생성 주의

기존 자동 인덱스부터 조회하기

학습용 MariaDB 서버의 shopcourse에 course_user로 접속합니다. 명령줄 클라이언트와 UTF-8 SQL 파일 실행 방법대로 코드를 UTF-8 파일에 저장하고 SOURCE C:/mariadb-lab/lesson.sql;처럼 실제 파일 경로로 실행할 수 있습니다. SQL 문은 운영체제 터미널이 아닌 MariaDB 연결 안에서 실행합니다.

먼저 SELECT VERSION(), DATABASE();와 SHOW TABLES;로 대상 서버·DB·이름 중복을 확인합니다. 아래 c39_customers, c39_products, c39_orders, c39_items는 이 글의 독립된 연습용 이름입니다. 이미 존재하면 정의와 행을 확인하고, 보관할 자료가 없는 이 글의 실습 테이블임이 확실할 때만 마지막 절의 정리 순서로 지운 뒤 다시 만듭니다. 기존 강좌의 동명 없는 테이블이나 운영 데이터는 삭제하지 않습니다.

USE shopcourse;
CREATE TABLE c39_customers (
 customer_id INT NOT NULL PRIMARY KEY,
 customer_name VARCHAR(50) NOT NULL,
 city VARCHAR(30) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE c39_products (
 product_id INT NOT NULL PRIMARY KEY,
 product_name VARCHAR(100) NOT NULL,
 category VARCHAR(30) NOT NULL,
 price DECIMAL(10,2) NOT NULL CHECK (price >= 0),
 stock INT NOT NULL CHECK (stock >= 0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE c39_orders (
 order_id INT NOT NULL PRIMARY KEY,
 customer_id INT NOT NULL,
 ordered_at DATETIME NOT NULL,
 status VARCHAR(20) NOT NULL,
 shipping_fee DECIMAL(10,2) NOT NULL DEFAULT 1000,
 CONSTRAINT fk_c39_order_customer FOREIGN KEY (customer_id)
  REFERENCES c39_customers(customer_id) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE c39_items (
 order_id INT NOT NULL,
 product_id INT NOT NULL,
 quantity INT NOT NULL CHECK (quantity > 0),
 unit_price DECIMAL(10,2) NOT NULL CHECK (unit_price >= 0),
 PRIMARY KEY (order_id, product_id),
 CONSTRAINT fk_c39_item_order FOREIGN KEY (order_id)
  REFERENCES c39_orders(order_id) ON DELETE RESTRICT,
 CONSTRAINT fk_c39_item_product FOREIGN KEY (product_id)
  REFERENCES c39_products(product_id) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO c39_customers VALUES
 (1,'김민수','서울'),(2,'이서연','부산'),
 (3,'박지우','서울'),(4,'최하늘','대전');
INSERT INTO c39_products VALUES
 (1,'노트','문구',3500,20),(2,'볼펜','문구',1500,50),
 (3,'머그컵','생활',8000,12),(4,'스탠드','생활',25000,5);
INSERT INTO c39_orders
 (order_id,customer_id,ordered_at,status,shipping_fee) VALUES
 (101,1,'2026-01-10 10:00:00','paid',1000),
 (102,1,'2026-01-20 11:00:00','paid',1000),
 (103,2,'2026-02-05 12:00:00','paid',1000),
 (104,3,'2026-02-10 13:00:00','canceled',1000);
INSERT INTO c39_items VALUES
 (101,1,2,3500),(101,2,3,1500),(102,3,1,8000),
 (103,1,1,3500),(103,3,2,8000),(104,2,1,1500);
SHOW INDEX FROM c39_orders;

주문 기본키와 고객 외래키를 위해 필요한 인덱스가 이미 있을 수 있습니다. 자동 생성 여부와 이름을 실제 출력으로 확인하고 이름만 보고 내용이 다르다고 판단하지 않습니다. 복합키의 열 순서는 Seq_in_index로 읽습니다.

고객·시각·번호 순으로 복합 인덱스 만들기

CREATE INDEX idx_c39_customer_date ON c39_orders(customer_id,ordered_at,order_id);
EXPLAIN SELECT order_id,ordered_at,status FROM c39_orders
WHERE customer_id=1 AND ordered_at>='2026-01-01' AND ordered_at<'2026-02-01'
ORDER BY ordered_at,order_id;
SELECT order_id FROM c39_orders
WHERE customer_id=1 AND ordered_at>='2026-01-01' AND ordered_at<'2026-02-01'
ORDER BY ordered_at,order_id;
SHOW INDEX FROM c39_orders;

예제 결과는 주문 101·102번입니다. customer_id는 같은 고객으로 범위를 좁히고 ordered_at은 기간을 제한하며 order_id는 같은 시각의 순서를 명확히 합니다. 작은 네 행에서는 최적화기가 전체 스캔을 선택해도 이상하지 않으므로, 이 데이터만으로 성능 향상을 보장하지 않습니다. 조회에 맞는 인덱스 설계 안내를 참고하세요.

선두 열·범위·커버링을 단정하지 않고 읽기

복합 인덱스는 일반적으로 왼쪽 선두 열부터 조건을 활용하는지가 중요합니다. 고객 조건 없이 날짜만 지정한 조회가 같은 효율적인 범위 접근을 사용한다고 가정하지 않습니다. 그렇다고 선두 조건이 없으면 어떤 형태의 인덱스 읽기도 절대 불가능하다고 단정할 수는 없습니다.

범위 조건 뒤의 열, 정렬 방향, 문자열 앞부분 검색, 결과 열 포함 여부에 따라 사용 방식이 달라집니다. 필요한 열이 인덱스에 포함되면 테이블 행 접근을 줄이는 커버링이 가능할 수 있지만 무조건 모든 열을 인덱스에 넣으면 키 크기와 쓰기 비용이 커집니다. 실제 EXPLAIN의 선택 키와 Extra를 확인합니다.

불필요한 중복과 삭제 의존 관계 확인하기

인덱스 이름이 달라도 열·순서·고유성·접두 길이가 같거나 선두가 겹칠 수 있습니다. 기존 고객 외래키용 인덱스와 새 복합 인덱스가 어떻게 관리되는지도 SHOW INDEX로 확인합니다. 지원 인덱스가 하나뿐인 외래키를 남긴 채 해당 인덱스를 삭제하면 제약 유지 때문에 거절될 수 있습니다.

삭제가 필요하면 실제 제약의 지원 키와 다른 쿼리 사용 여부를 먼저 확인하고 필요한 대체 인덱스를 준비합니다. 기본키·UNIQUE는 단순 성능 도구만이 아니므로 사용되지 않는 것처럼 보여도 업무상 고유 조건을 함부로 없애지 않습니다.

같은 조회 결과와 실행 계획을 대조하기

인덱스를 추가해도 결과 행과 정렬 의미는 같아야 합니다. 대표 쿼리와 데이터 분포에서 실행 계획·실제 읽은 행·지연·쓰기 부담을 비교하고, 테스트 환경과 측정 방법을 기록합니다. 캐시가 데워진 두 번째 실행만 보고 인덱스의 효과라고 결론 내리지 않습니다.

반복하기 전 현재 DB와 실제 정의·행을 확인하고 필요한 자료를 백업합니다. 보관할 데이터가 없는 이 글의 전용 실습 객체만 아래 순서로 정리합니다. 외래키가 있으면 자식부터 지웁니다. DROP TABLE은 구조와 데이터를 모두 없애며 일반적인 ROLLBACK으로 복원되지 않으므로 취소 가능한 DML 실습과 분리합니다.

DROP TABLE shopcourse.c39_items;
DROP TABLE shopcourse.c39_orders;
DROP TABLE shopcourse.c39_products;
DROP TABLE shopcourse.c39_customers;
같은 카테고리의 다른 글
MariaDB / 피벗 테이블 만드는 방법

MariaDB / 피벗 테이블 만드는 방법

피벗 테이블은 엑셀의 유용한 기능 중의 하나입니다. 예를 들어 아래의 왼쪽 데이터를 오른쪽 표처럼 만드는 것이 피벗 테이블입니다. 조건에 따라 그룹화시켜서 보기 좋게 데이터를 정리하는 것이라 보면 됩니다. MariaDB에서도 몇 가지 함수를 이용하여 피벗 테이블을 만들 수 있습니다.

MariaDB 강좌 / Ubuntu에 MariaDB 설치하고 서버 실행하기

MariaDB 강좌 / Ubuntu에 MariaDB 설치하고 서버 실행하기

Ubuntu APT 설치 후 서버 실행·자동 시작·관리자 소켓 인증을 확인합니다. 로컬 TCP 학습 계정을 만들고 패키지 버전, 인증 오류와 서비스 로그를 확인하는 절차를 설명합니다.

MariaDB 강좌 / 윈도 함수로 순번과 순위 계산하기

MariaDB 강좌 / 윈도 함수로 순번과 순위 계산하기

고객별 금액의 ROW_NUMBER·RANK·DENSE_RANK를 비교하고 지역별 순위와 바깥 조회의 상위 N개 필터를 실습합니다. 공동 순위와 행 개수 제한의 차이를 결과로 확인합니다.

MariaDB 강좌 / WITH로 공통 테이블 식 만들고 쿼리 나누기

MariaDB 강좌 / WITH로 공통 테이블 식 만들고 쿼리 나누기

WITH의 공통 테이블 식으로 결제 주문·주문별 합계·고객 연결을 단계별로 나눕니다. 중간 결과 검증, 한 문장 안의 유효 범위와 성능을 자동 보장하지 않는 이유를 설명합니다.

MariaDB 강좌 / 서버 설정·SQL 모드·시간대·로그 확인하기

MariaDB 강좌 / 서버 설정·SQL 모드·시간대·로그 확인하기

설정 파일·GLOBAL·SESSION의 차이를 기준으로 MariaDB 동작을 확인합니다. SQL 모드 오류와 TIMESTAMP·DATETIME 시간대 실습, 로그 조건 점검 및 변경 복구 절차를 제공합니다.

MariaDB 강좌 / 저장 프로시저·함수·트리거의 차이와 사용 방법

MariaDB 강좌 / 저장 프로시저·함수·트리거의 차이와 사용 방법

프로시저·함수·트리거의 호출 방식과 권한 차이를 재고·금액·변경 기록 예제로 비교합니다. 전체 생성 SQL과 실행 결과, 롤백·확정·오류 확인 및 객체 정리 방법을 제공합니다.

MariaDB 강좌 / LEFT JOIN으로 연결되지 않은 행도 조회하기

MariaDB 강좌 / LEFT JOIN으로 연결되지 않은 행도 조회하기

LEFT JOIN으로 주문 없는 고객을 유지하고 paid 주문 수를 0까지 표시합니다. ON·WHERE의 필터 위치, COUNT(*)의 함정, NULL 기본키로 미일치 행을 찾는 기준을 실습합니다.

MariaDB 강좌 / Python에서 MariaDB 연결하고 매개변수 쿼리 사용하기

MariaDB 강좌 / Python에서 MariaDB 연결하고 매개변수 쿼리 사용하기

PyMySQL로 연결·삽입·조회·확정·취소를 수행하는 전체 Python 예제를 제공합니다. 따옴표·한글 입력과 SQL 인젝션 형태의 문자열을 값으로 다루고 정렬 열은 허용 목록으로 제한합니다.

MariaDB / VIEW

MariaDB / VIEW

VIEW는 가상의 테이블입니다. 실제로 데이터를 저장하지 않고, 기존의 하나 이상의 테이블을 기반으로 한 쿼리 결과를 테이블처럼 사용할 수 있게 해줍니다. 뷰는 복잡한 쿼리를 단순화하고, 보안 및 데이터 관리에 유용하게 사용됩니다.

MariaDB 강좌 / MariaDB란? 데이터베이스·SQL·MySQL과의 관계

MariaDB 강좌 / MariaDB란? 데이터베이스·SQL·MySQL과의 관계

MariaDB 서버·클라이언트와 데이터베이스·테이블·행·열의 관계를 상품 예제로 이해합니다. SQL의 역할, MySQL과의 관계, 이후 강좌에서 사용할 실습 환경을 구분합니다.