루스 인덱스 스캔은 인덱스의 모든 레코드를 순차적으로 읽지 않고, 필요한 부분만 "느슨하게(Loose)" 건너뛰면서 읽는 스캔 방식입니다.
일반 인덱스 스캔(Tight Index Scan)이 인덱스를 처음부터 끝까지 읽는 것과 달리, 루스 스캔은 그룹의 경계를 넘어뛰면서 각 그룹의 대표 값만 읽습니다.
주로 GROUP BY와 MIN()/MAX() 집계 함수를 사용하는 쿼리에서 자동으로 적용됩니다.
인덱스 스캔 방식의 종류
GROUP BY와 집계 함수
GROUP BY는 특정 컬럼 값으로 레코드를 그룹핑하여 집계하는 SQL 구문입니다.
SELECT dept_id, MIN(salary)
FROM employees
GROUP BY dept_id;
일반적으로는 모든 레코드를 읽어 그룹핑하지만, 인덱스가 있고 특정 조건을 만족하면 루스 스캔이 사용됩니다.
복합 인덱스의 정렬 특성
복합 인덱스 INDEX(dept_id, salary)는 dept_id로 먼저 정렬되고, 같은 dept_id 내에서 salary로 정렬됩니다. 이 특성 덕분에 각 그룹의 첫 레코드만 읽어도 MIN() 값을 알 수 있습니다.
동작 원리 상세
복합 인덱스 INDEX(category_id, price)가 있고 다음 쿼리를 실행한다고 가정합시다:
SELECT category_id, MIN(price)
FROM products
GROUP BY category_id;
타이트 스캔 (일반 방식)
category_id=1인 모든 레코드 읽기 (100개)category_id=2인 모든 레코드 읽기 (200개)category_id=3인 모든 레코드 읽기 (150개)루스 스캔 (최적화)
category_id=1의 첫 레코드만 읽기 (MIN)category_id=2로 점프, 첫 레코드만 읽기category_id=3으로 점프, 첫 레코드만 읽기루스 스캔이 가능한 조건
GROUP BY 컬럼이 인덱스의 왼쪽 접두사여야 함MIN(), MAX() 집계 함수만 사용 가능SUM(), AVG(), COUNT(*)는 불가 (모든 레코드 필요)실행 계획 확인
EXPLAIN SELECT category_id, MIN(price)
FROM products
GROUP BY category_id;
-- Extra: Using index for group-by (scanning)
성능 이득
| 상황 | 타이트 스캔 | 루스 스캔 | 성능 차이 |
|---|---|---|---|
| 100만 레코드, 100개 그룹 | 100만 개 읽기 | 100개 읽기 | 1만 배 |
| 10만 레코드, 10개 그룹 | 10만 개 읽기 | 10개 읽기 | 1만 배 |
| 1만 레코드, 1천 개 그룹 | 1만 개 읽기 | 1천 개 읽기 | 10배 |
그룹 수가 적고 각 그룹의 레코드 수가 많을수록 루스 스캔의 효과가 극대화됩니다.
루스 스캔이 불가능한 경우
-- ❌ SUM은 모든 레코드 필요
SELECT category_id, SUM(price) FROM products GROUP BY category_id;
-- ❌ price는 인덱스의 두 번째 컬럼 (왼쪽 접두사 아님)
SELECT price, MIN(quantity) FROM products GROUP BY price;
-- ❌ COUNT(*)는 모든 레코드 필요
SELECT category_id, COUNT(*) FROM products GROUP BY category_id;
루스 스캔은 각 그룹의 첫 레코드만 읽고 나머지는 건너뛰어 극적인 성능 향상을 제공합니다.