MySQL에서 특정 기간의 데이터를 조회할 때 다음과 같은 SQL을 자주 사용합니다.
SELECT *
FROM orders
WHERE created_at BETWEEN '2026-08-01' AND '2026-08-31';
문법도 간단하고 읽기도 쉽습니다.
그런데 한 가지 궁금한 점이 생깁니다.
BETWEEN은 실제로 어떻게 데이터를 찾을까요?
그리고 다음 조건은
WHERE created_at BETWEEN ...
다음과 어떤 차이가 있을까요?
WHERE created_at >= ...
AND created_at <= ...
더 중요한 것은 created_at에 인덱스가 있다면 MySQL은 이 범위를 인덱스에서 어떻게 찾느냐는 것입니다.
이번 글에서는 BETWEEN 자체의 문법만 설명하는 것이 아니라 범위 조건이 B-Tree 인덱스에서 어떻게 동작하는지를 중심으로 살펴보겠습니다.
1. BETWEEN은 어떤 의미일까?
가장 기본적인 형태부터 보겠습니다.
SELECT *
FROM products
WHERE price BETWEEN 10000 AND 30000;
이 조건은 다음과 같은 의미입니다.
10000 이상
그리고
30000 이하
즉 논리적으로는 다음과 같습니다.
WHERE price >= 10000
AND price <= 30000
BETWEEN은 양쪽 경계를 포함합니다.
예를 들어 데이터가 다음과 같다면,
9000
10000
15000
30000
31000
다음 조건은
WHERE price BETWEEN 10000 AND 30000
다음 값을 가져옵니다.
10000
15000
30000
10000과 30000 모두 포함됩니다.
2. BETWEEN과 비교 연산자는 같은 것일까?
다음 두 조건을 비교해보겠습니다.
WHERE price BETWEEN 10000 AND 30000
WHERE price >= 10000
AND price <= 30000
범위의 의미는 동일합니다.
즉 BETWEEN은 복잡한 새로운 검색 방법이라기보다는 범위를 표현하기 편리한 SQL 문법이라고 생각하면 됩니다.
다만 실무에서는 경계값을 어떻게 처리할 것인지가 중요합니다.
특히 날짜와 시간에서는 BETWEEN을 사용할 때 주의해야 합니다.
3. 날짜 검색에서 BETWEEN이 헷갈리는 이유
예를 들어 2026년 8월의 데이터를 가져온다고 해보겠습니다.
SELECT *
FROM orders
WHERE created_at BETWEEN '2026-08-01' AND '2026-08-31';
개발자가 생각하기에는
2026-08-01 전체
~
2026-08-31 전체
를 가져올 것처럼 보입니다.
하지만 created_at이 DATETIME이라면 이야기가 달라집니다.
'2026-08-31'은 날짜만 표현하고 있습니다.
MySQL에서 이 값이 DATETIME과 비교될 때 시간 부분이 의도한 것과 다르게 처리될 수 있기 때문에, 날짜 전체를 정확하게 포함하려는 목적이라면 주의해야 합니다.
예를 들어 다음 데이터가 있다고 해보겠습니다.
2026-08-31 00:00:00
2026-08-31 10:30:00
2026-08-31 18:20:00
2026-08-31 23:59:59
개발자가 원하는 것은 8월 31일 하루 전체일 가능성이 높습니다.
이런 경우에는 날짜의 끝을 억지로 만들어서 사용하는 것보다 다음 날 00시를 기준으로 범위를 닫는 방식이 더 명확합니다.
WHERE created_at >= '2026-08-01 00:00:00'
AND created_at < '2026-09-01 00:00:00'
이 방식은 8월 전체를 정확하게 표현합니다.
4. 범위 조건은 어떻게 생각하면 될까?
범위 조건을 수학적으로 생각하면 이해하기 쉽습니다.
다음 조건을 보겠습니다.
WHERE price >= 10000
AND price < 30000
이것을 다음과 같이 표현할 수 있습니다.
[10000, 30000)
의미는
10000 포함
30000 제외
입니다.
이 방식의 장점은 경계값을 명확하게 만들 수 있다는 것입니다.
날짜에도 똑같이 적용할 수 있습니다.
[2026-08-01 00:00:00,
2026-09-01 00:00:00)
즉
WHERE created_at >= '2026-08-01 00:00:00'
AND created_at < '2026-09-01 00:00:00'
이면 정확하게 8월 전체를 의미합니다.
5. B-Tree 인덱스는 범위를 어떻게 찾을까?
이제 인덱스 이야기를 해보겠습니다.
다음과 같은 인덱스가 있다고 가정하겠습니다.
CREATE INDEX idx_orders_created_at
ON orders (created_at);
B-Tree 인덱스는 값을 정렬된 형태로 탐색할 수 있는 구조입니다.
개념적으로 단순화하면 다음과 같습니다.
2026-07-29
2026-07-30
2026-07-31
2026-08-01
2026-08-02
2026-08-03
...
2026-08-30
2026-08-31
2026-09-01
2026-09-02
만약 다음 조건으로 조회한다면,
WHERE created_at >= '2026-08-01'
AND created_at < '2026-09-01'
MySQL은 인덱스에서 8월 1일에 해당하는 위치를 찾은 다음 해당 범위를 따라갈 수 있습니다.
개념적으로는 다음과 같습니다.
2026-07-31
↓
2026-08-01 ← 시작 위치
↓
2026-08-02
↓
2026-08-03
↓
...
↓
2026-08-31
↓
2026-09-01 ← 여기서 범위 종료
이것이 인덱스의 중요한 특징입니다.
정확히 하나의 값만 찾는 것이 아니라 특정 범위의 값을 탐색할 수 있습니다.
6. = 조건과 범위 조건은 다르다
인덱스를 설명할 때 흔히 다음과 같은 조건을 먼저 생각합니다.
WHERE id = 100;
이것은 특정 값을 찾는 조건입니다.
반면 다음은 범위입니다.
WHERE id >= 100
AND id < 200;
둘 다 인덱스를 사용할 수 있는 조건이지만 검색하는 방식은 다릅니다.
개념적으로 보면
= 조건
100
↓
특정 위치를 찾음
이고,
범위 조건
100
↓
시작 위치를 찾음
↓
101
102
103
...
199
↓
범위 종료
와 같이 생각할 수 있습니다.
즉 B-Tree 인덱스는 범위 검색에도 매우 중요한 역할을 합니다.
7. EXPLAIN으로 직접 확인하기
테스트 테이블을 준비합니다.
DROP TABLE IF EXISTS orders;
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
created_at DATETIME NOT NULL,
price INT NOT NULL
) ENGINE=InnoDB;
그리고 인덱스를 추가합니다.
CREATE INDEX idx_orders_created_at
ON orders (created_at);
테스트 데이터가 충분히 들어있다고 가정하고 다음 SQL을 실행합니다.
EXPLAIN
SELECT *
FROM orders
WHERE created_at >= '2026-08-01 00:00:00'
AND created_at < '2026-09-01 00:00:00';
실행 계획에서 다음 항목을 확인합니다.
type
possible_keys
key
rows
Extra
특히 key와 rows를 확인하면 MySQL이 어떤 인덱스를 선택했고 어느 정도의 데이터를 확인할 것으로 예상하는지 살펴볼 수 있습니다.
실제 실행 결과는 테스트 환경에서 직접 확인해야 합니다.
데이터 양이나 분포에 따라 실행 계획은 달라질 수 있기 때문입니다.
8. 범위가 너무 넓으면 어떻게 될까?
여기서 중요한 부분이 있습니다.
인덱스가 있으면 범위 검색은 무조건 빠르다.
이것도 정확하지 않습니다.
예를 들어 데이터가 1,000만 건 있다고 해보겠습니다.
그중 특정 날짜에 해당하는 데이터가 100건이라면 인덱스로 범위를 좁히는 것이 상당히 유리할 수 있습니다.
반대로 다음과 같은 조건이라면 어떨까요?
WHERE created_at >= '2020-01-01'
데이터 대부분이 이 조건에 해당할 수 있습니다.
그러면 인덱스를 사용하더라도 매우 많은 데이터를 확인해야 할 수 있습니다.
결국 중요한 것은
인덱스가 존재하는가?
만이 아닙니다.
실제로는
검색 범위가 얼마나 넓은가?
+
조건에 해당하는 데이터가 얼마나 많은가?
+
옵티마이저가 어떤 실행 계획을 선택하는가?
를 함께 봐야 합니다.
9. 선택도가 범위 검색에도 영향을 준다
예를 들어 status라는 컬럼이 있다고 해보겠습니다.
success
success
success
success
success
failed
success
success
대부분의 데이터가 success라면
WHERE status = 'success'
라는 조건으로 많은 데이터를 가져오게 됩니다.
반대로 특정 값에 해당하는 데이터가 매우 적다면 검색 범위를 크게 줄일 수 있습니다.
날짜 범위도 마찬가지입니다.
WHERE created_at >= '2026-08-31 00:00:00'
AND created_at < '2026-09-01 00:00:00'
이 조건에 해당하는 데이터가 전체의 극히 일부라면 인덱스가 큰 도움이 될 수 있습니다.
하지만 전체 데이터 대부분이 하루 안에 몰려 있다면 기대했던 만큼 효과가 크지 않을 수도 있습니다.
따라서 인덱스 성능을 판단할 때는 실제 데이터 분포가 중요합니다.
10. 날짜 범위 검색에서 자주 하는 실수
가장 흔한 실수 중 하나는 날짜의 마지막 시간을 직접 지정하는 것입니다.
WHERE created_at >= '2026-08-01 00:00:00'
AND created_at <= '2026-08-31 23:59:59'
겉으로 보기에는 문제가 없어 보입니다.
하지만 시간 정밀도를 생각하면 애매해질 수 있습니다.
예를 들어 데이터가 마이크로초 단위까지 저장될 수 있다면 다음 값은 어떨까요?
2026-08-31 23:59:59.500000
또는
2026-08-31 23:59:59.999999
이런 데이터를 정확하게 포함하려면 마지막 순간을 어디까지 지정할 것인지 고민해야 합니다.
그래서 다음과 같은 형태가 더 명확합니다.
WHERE created_at >= '2026-08-01 00:00:00'
AND created_at < '2026-09-01 00:00:00'
다음 구간의 시작을 종료점으로 사용하는 방식입니다.
11. BETWEEN을 사용할 때 특히 주의할 것
BETWEEN은 양쪽 값을 모두 포함합니다.
WHERE price BETWEEN 10000 AND 30000
은
WHERE price >= 10000
AND price <= 30000
과 같은 의미입니다.
따라서 경계값을 제외하고 싶다면 BETWEEN보다는 비교 연산자를 사용하는 것이 명확합니다.
예를 들어
WHERE price >= 10000
AND price < 30000
처럼 작성하면
10000 포함
30000 제외
라는 의미를 명확하게 표현할 수 있습니다.
특히 날짜/시간 범위에서는 이런 방식이 유용합니다.
12. BETWEEN이 나쁜 문법은 아니다
그렇다고 BETWEEN을 사용하면 안 된다는 의미는 아닙니다.
다음처럼 숫자 범위를 표현할 때는 상당히 읽기 쉽습니다.
SELECT *
FROM products
WHERE price BETWEEN 10000 AND 30000;
또는 특정 ID 범위를 조회할 때도 자연스럽습니다.
SELECT *
FROM users
WHERE id BETWEEN 1000 AND 2000;
이런 경우라면 BETWEEN을 사용하는 것이 오히려 SQL의 의도를 쉽게 전달할 수 있습니다.
중요한 것은 BETWEEN 자체가 느린지 여부가 아니라 어떤 범위를 표현하고 있는지입니다.
13. 인덱스에서 범위 검색이 중요한 이유
앞서 작성했던 인덱스 관련 글에서는 인덱스의 기본 구조와 여러 인덱스 활용 방법을 살펴봤습니다.
이번 글에서 중요한 부분은 조금 다릅니다.
인덱스는 단순히
id = 100
같은 정확한 값을 찾는 데만 사용하는 것이 아닙니다.
다음과 같은 범위도 찾을 수 있습니다.
WHERE id >= 100
AND id < 200
WHERE created_at >= '2026-08-01'
AND created_at < '2026-09-01'
WHERE price >= 10000
AND price < 30000
즉 실무에서 자주 사용하는
날짜 조회
가격 조회
ID 범위 조회
숫자 범위 조회
등에서 인덱스의 범위 탐색이 중요한 역할을 합니다.
14. 실제 성능은 반드시 실행 계획으로 확인해야 한다
이번 테스트에서 가장 중요한 부분입니다.
다음 쿼리를 실행했다고 해서
SELECT *
FROM orders
WHERE created_at >= '2026-08-01'
AND created_at < '2026-09-01';
무조건 빠르다고 결론내리면 안 됩니다.
실제 데이터가 충분히 많은 환경에서 EXPLAIN을 확인해야 합니다.
EXPLAIN
SELECT *
FROM orders
WHERE created_at >= '2026-08-01'
AND created_at < '2026-09-01';
MySQL 8.0에서는 다음도 사용할 수 있습니다.
EXPLAIN ANALYZE
SELECT *
FROM orders
WHERE created_at >= '2026-08-01'
AND created_at < '2026-09-01';
여기서 실제 결과를 확인합니다.
특히 다음을 봅니다.
실제로 어떤 인덱스를 사용했는가?
예상 Row 수는 얼마나 되는가?
실제 처리한 Row는 얼마나 되는가?
실행 시간은 어느 정도인가?
여기서 나온 결과는 반드시 현재 테스트 환경에서 직접 실행한 값을 기준으로 판단해야 합니다.
테스트하지 않은 실행 시간이나 Row 수를 임의로 작성해서는 안 됩니다.
15. 범위 검색에서 기억해야 할 것
이번 내용을 정리하면 다음과 같습니다.
BETWEEN
WHERE price BETWEEN 10000 AND 30000
은
WHERE price >= 10000
AND price <= 30000
와 같은 의미입니다.
양쪽 경계값을 모두 포함합니다.
날짜 범위
날짜/시간을 조회할 때는 다음과 같이 다음 구간의 시작을 종료점으로 사용하는 방법을 고려할 수 있습니다.
WHERE created_at >= '2026-08-01 00:00:00'
AND created_at < '2026-09-01 00:00:00'
이렇게 하면 시간의 마지막 순간을 직접 계산할 필요가 없습니다.
인덱스와 범위 검색
B-Tree 인덱스는 특정 값뿐 아니라 범위도 탐색할 수 있습니다.
시작 위치 찾기
↓
범위 안의 값 탐색
↓
범위 종료
따라서 범위 조건은 인덱스를 활용하는 중요한 검색 형태입니다.
하지만 범위가 매우 넓다면 인덱스의 효과가 작아질 수도 있습니다.
마무리
BETWEEN은 매우 간단한 SQL 문법처럼 보입니다.
WHERE price BETWEEN 10000 AND 30000
하지만 실제 데이터베이스에서는 범위 검색이라는 중요한 개념과 연결되어 있습니다.
특히 B-Tree 인덱스는 특정 값 하나만 찾는 것이 아니라,
시작점
↓
범위 탐색
↓
종료점
형태로 데이터를 검색할 수 있습니다.
그래서 다음과 같은 조건은 실무에서 매우 중요합니다.
WHERE created_at >= '2026-08-01 00:00:00'
AND created_at < '2026-09-01 00:00:00'
다만
범위 조건이면 무조건 빠르다.
또는
인덱스가 있으면 무조건 인덱스를 사용하는 것이 좋다.
라고 생각해서는 안 됩니다.
실제 데이터의 양과 분포에 따라 결과가 달라질 수 있기 때문입니다.
결국 지금까지의 글과 마찬가지로 가장 중요한 습관은 이것입니다.
쿼리를 작성한다
↓
EXPLAIN으로 확인한다
↓
실제 검색 범위를 확인한다
↓
인덱스가 어떻게 사용되는지 확인한다
↓
필요하면 조건이나 인덱스를 수정한다
↓
다시 확인한다
SQL의 문법만 아는 것과 데이터베이스가 그 조건을 실제로 어떻게 처리하는지 이해하는 것은 다릅니다.
BETWEEN 하나를 사용하더라도 결국 중요한 것은 어떤 범위를 검색하고 있으며, MySQL이 그 범위를 어떻게 찾고 있는가입니다.
'개발 > 데이터베이스' 카테고리의 다른 글
| MySQL OR 조건과 인덱스 - OR 때문에 쿼리가 느려지는 이유 (0) | 2026.09.02 |
|---|---|
| MySQL DISTINCT 제대로 이해하기 - 중복 제거가 왜 느려질까? (0) | 2026.09.01 |
| MySQL 함수 때문에 인덱스를 못 타는 경우 - WHERE 조건을 어떻게 작성해야 할까? (0) | 2026.08.31 |
| MySQL LIKE 검색과 인덱스 - 어떤 검색은 왜 인덱스를 못 탈까? (0) | 2026.08.31 |
| MySQL LIMIT 성능 제대로 이해하기 - LIMIT 20인데 왜 느릴까? (0) | 2026.08.28 |