Home range 검색과 filesort
Post
Cancel

range 검색과 filesort


회사 프로젝트 중, filesort가 발생했습니다. 그런데 filesort의 개념과 발생 원인을 한 문장으로 정의 하지 못한 것을 보고 이를 정리하고 싶어 글을 작성하게 되었습니다.





1. B-Tree의 특성: 사전식 정렬


B-Tree 인덱스는 앞에 선언된 컬럼부터 차례대로 사전식으로 정렬되어 저장됩니다. 즉, 첫 번째 컬럼의 값이 가장 큰 정렬 기준이 되고, 첫 번째 컬럼의 값이 같을 때만 두 번째 컬럼이 의미 있는 순서를 가지며, 두 번째 컬럼까지 같을 때에만 세 번째 컬럼이 정렬됩니다. 이 때문에 인덱스가 제공하는 정렬 순서는 항상 앞 컬럼 값이 고정되어 있을 때만 신뢰할 수 있습니다.

1
CREATE INDEX idx_example ON orders (order_date, status, order_id);

order_date → status → order_id 순서로 정렬 됩니다.




컬럼을 = 를 사용해 하나의 값으로 고정 하면, 인덱스 내부에서는 해당 값에 해당하는 값/구간이 연속된 하나의 범위가 됩니다. 이 내부에서는 다음 컬럼들이 이미 정렬된 상태이기 때문에, 별도의 정렬 작업 없이도 정렬 조건(ORDER BY) 을 만족 할 수 있습니다.

1
2
3
4
SELECT *
FROM orders
WHERE order_date = '2025-01-01'
ORDER BY status, order_id;

order_date가 ‘2025-01-01’로 고정되었으므로, status와 order_id는 이미 정렬된 상태입니다.





2. 범위 조건과 filesort


하지만 앞 컬럼에 범위 조건 이 들어오는 순간 상황이 달라집니다.

1
CREATE INDEX idx_orderdate_status ON orders (order_date, status);




>=, <, BETWEEN 과 같은 조건은 하나의 값이 아니라 여러 값을 동시에 허용합니다. 이 경우 인덱스에서는 여러 개의 서로 다른 구간을 연속으로 읽게 되며, 각 구간 내부에서는 정렬이 유지 되지만 구간과 구간 사이에서는 정렬이 보장되지 않습니다. 결과적으로 전체 결과 집합 기준으로 보면 뒤 컬럼의 순서는 섞이게 되죠.

1
2
3
4
5
SELECT *
FROM orders
WHERE order_date >= '2025-01-01'
AND order_date <  '2025-01-10'
ORDER BY status;




이 때문에 MySQL은 인덱스 컬럼 중 처음으로 range 조건이 등장하는 지점까지만 정렬을 신뢰합니다. 그 이후 컬럼에 대해서는 인덱스 순서가 정렬을 보장하지 않는다고 판단하고, ORDER BY가 있으면 별도의 정렬을 수행합니다. 예를 들어, 다음과 같이 인덱스가 존재한다고 가정해보겠습니다.

1
CREATE INDEX idx_example2 ON orders (order_date, status, order_id);




order_date에서 range 검색을 하기 때문에 order_id 정렬은 인덱스로 보장 불가이므로 filesort가 발생합니다.

1
2
3
4
SELECT *
FROM orders
WHERE order_date BETWEEN '2025-01-01' AND '2025-01-31'
ORDER BY order_id;





3. 어떤 문제가 발생했는데?


회사에서 대략 아래와 같은 쿼리를 실행했는데, filesort가 발생했습니다. modified_at에 인덱스가 걸려 있었기 때문에, 실행 계획에는 인덱스를 잘 타고있었고요. 당연히 인덱스가 걸린 칼럼으로 정렬했으니 filesort가 안 발생할 줄 알았죠. 🤔

1
2
3
4
5
6
SELECT *
FROM order_detail AS od
WHERE od.modified_at >= '2025-12-31 00:00:00' 
    AND od.modified_at < '2026-01-01 00:00:00'
    AND od.status IN('READY', 'PAID', 'CANCELLED')
ORDER BY status;




원인을 살펴보면 modified_at에 범위 조건이 적용되면, 데이터를 modified_at으로 정렬된 순서로 읽습니다. 하지만 이는 ORDER BY 절에서 원한 status 기준 정렬이 아니기 때문에, MySQL은 내부적으로 별도의 정렬(filesort)을 한 번 더 수행합니다. 그래서 인덱스는 탔지만 Using filesort 가 발생한 거고요.

1
2
3
4
5
+----+-------------+--------------+------------+-------+---------------------+---------------------+---------+------+------+----------+---------------------------------------+
| id | select_type | table        | partitions | type  | possible_keys       | key                 | key_len | ref  | rows | filtered | Extra                                 |
+----+-------------+--------------+------------+-------+---------------------+---------------------+---------+------+------+----------+---------------------------------------+
|  1 | SIMPLE      | order_detail | NULL       | range | idx_modified_status | idx_modified_status | 5       | NULL |    5 |   100.00 | Using index condition; Using filesort |
+----+-------------+--------------+------------+-------+---------------------+---------------------+---------+------+------+----------+---------------------------------------+





4. 정리


filesort는 MySql이 데이터베이스 내부적으로 한 번 더 자체 정렬 하는 기능 입니다. 클라이언트가 제공한 조건으로 정렬이 한 번에 안되니까요. 알고 있는 개념을 한 문장으로 정의하는건 생각보다 어려운데, 2026년에도 계속해서 해봐야죠. 🚀


This post is licensed under CC BY 4.0 by the author.