使用数据库时,我们经常会用到范围查询,今天来聊一聊 MySQL 的 BETWEEN AND 语句。
1.是否包含边界
为了说明 BETWEEN AND 语句是否包括边界,我们先创建一张表,SQL 如下:
复制
CREATE TABLE`test3` (
`id`int(8) NOTNULL AUTO_INCREMENT,
`a`varchar(10) COLLATE utf8_bin DEFAULTNULL,
`b`varchar(10) COLLATE utf8_bin DEFAULTNULL,
`date`dateDEFAULTNULL,
`date_time` datetime DEFAULTNULL,
`time_stamp`timestampNULLDEFAULTNULL,
PRIMARY KEY (`id`),
KEY`idx_a` (`a`)
) ENGINE=InnoDB AUTO_INCREMENT=11DEFAULTCHARSET=utf8 COLLATE=utf8_bin1.2.3.4.5.6.7.8.9.10.
我们插入 10 条数据,如下图:
图片
图片
1.1 执行结果
对 id 做范围查询
复制
SELECT * FROM test3 WHERE id BETWEEN 2 AND 6;1.
查询结果如下:
图片
图片
可以看到,对 id 做范围查询,包含了边界值 2 和 6,是一个闭区间。
对 date 做范围查询
复制
SELECT * FROM test3 WHERE DATE BETWEEN 2025-08-28 AND 2025-08-31;1.
查询结果如下:
图片
图片
可以看到,对 date 类型的字段进行范围查询,同样包含了边界值,是一个闭区间。
对 date_time 做范围查询
复制
SELECT * FROM test3 WHERE date_time BETWEEN 2025-08-28 AND 2025-08-31;1.
对 date_time 使用日期做范围查询,查询结果如下:
图片
图片
可以看到,这个查询漏掉了 2025-08-31 这一天的数据,是一个前闭后开的区间。
这是因为 MySQL 会自动将日期的时间部分补充为零点,所以上面的查询语句等价于:
复制
SELECT * FROM test3 WHERE date_time BETWEEN 2025-08-28 00:00:00 AND 2025-08-31 00:00:00;1.
为了不漏掉数据,可以将上面的查询语句改为:
复制
SELECT * FROM test3 WHERE DATE BETWEEN 2025-08-28 AND 2025-09-01;1.
对 time_stamp 使用日期做范围查询,结果跟 date_time 一样。
1.2 总结
BETWEEN AND 语句是包括边界值的,是一个前闭后闭的区间。BETWEEN A AND B 等价于 >= A AND <= B。注意时间类型的字段,DATETIME 和 TIMESTAMP,如果采用日期格式来查询,结果是前闭后开的区间。BETWEEN A AND B 等价于 >= A AND < B。为了避免漏掉数据,datetime 和 timestamp 使用日期做范围查询,可以在后面的日期上加 1 天。
2.原理
2.1 执行器优化
对于 BETWEEN AND 语句,MySQL 优化器会在早期优化时把它优化成等值查询,所以下面查询 SQL
复制
SELECT * FROM test3 WHERE id BETWEEN 2 AND 6;1.
在优化器看来完全等价与:
复制
SELECT * FROM test3 WHERE id >= 2 AND id <= 6;1.
两个 SQL 在性能上没有差别。
2.2 性能
索引范围扫描
如果 BETWEEN AND 语句的字段上有索引,则可以使用索引进行范围扫描,效率很高。我们看一下上面一个 SQL 执行计划如下:
复制
EXPLAIN SELECT * FROM test3 WHERE id BETWEEN 2 AND 6;1.
图片
图片
type=range,代码使用到了索引进行范围扫描,查询过程如下:
存储引擎从索引树的根节点开始,找到第一个 id >= 2 的索引条目。这个操作时间复杂度是 O(log n),非常高效;从上面找到的第一个索引条目开始,沿着索引树的叶子节点向后顺序扫描;当遇到第一个 id > 6 的索引条目时,停止扫描;存储引擎给执行器返回结果集。全表扫描
如果 BETWEEN AND 语句的字段上没有索引,只能进行全表扫描,比如下面的语句:
复制
EXPLAIN SELECT * FROM test3 WHERE b BETWEEN 30 AND 50;1.
我们看一下执行计划:
图片
图片
这就需要扫描表中的每一行记录,逐一判断字段 b 的值是否落在 [30, 50] 这个区间内。这个执行效率非常低。
非主键索引跟等值查询不同的是,即使 BETWEEN AND 语句的字段加了普通索引,如果查询的字段用不上覆盖索引,也会走全表扫描。如下面的语句:
复制
EXPLAIN SELECT * FROM test3 WHERE a BETWEEN 30 AND 50;1.
虽然字段 a 上面有普通索引,但是查询的是表 test3 中的所有字段,也是走不上 a 这个索引的。我们看一下执行计划:
图片
复合索引
我们再看一下复合索引的情况,我们新建一张表,SQL 如下:
复制
CREATE TABLE`test4` (
`id`int(8) NOTNULL AUTO_INCREMENT,
`a`varchar(10) COLLATE utf8_bin DEFAULTNULL,
`b`varchar(10) COLLATE utf8_bin DEFAULTNULL,
`date`dateDEFAULTNULL,
`date_time` datetime DEFAULTNULL,
`time_stamp`timestampNULLDEFAULTNULL,
PRIMARY KEY (`id`),
KEY`idx_a_b` (`a`, `b`)
) ENGINE=InnoDB AUTO_INCREMENT=11DEFAULTCHARSET=utf8 COLLATE=utf8_bin1.2.3.4.5.6.7.8.9.10.
在 test4 这张表上,我们建一个字段(a,b)的复合索引。 BETWEEN AND 语句是可以用最左前缀原则的。如果我们执行下面 SQL:
复制
SELECT a,b,id FROM test4 WHERE a BETWEEN 2 AND 6 AND b = 3;1.
复合索引的第一列 a 使用了范围查询,第二列 b 作等值查询。MySQL 可以利用索引快速找到字段 a 在范围[2, 6]之间的数据,然后在这些结果中筛选出 b = 3 的记录。这种情况下字段 a 使用了索引,但是字段 b 没有用上索引。
图片
如果我们执行下面 SQL:
复制
SELECT a,b,id FROM test4 WHERE a = 3 and b BETWEEN 2 AND 6;1.
复合索引的第一列 a 使用了等值查询,第二列 b 做范围查询。这种情况也是可以走上索引的。
图片
如果我们执行下面 SQL:
复制
SELECT a,b,id FROM test4 WHERE b BETWEEN 2 AND 6;1.
这种情况不符合最左前缀原则,所以是走不上索引的。
图片
索引下推
使用 BETWEEN AND 语句不能走索引下推。
我们再来创建一张表:
复制
CREATE TABLE`test_temp` (
`id`INT(11) NOTNULLDEFAULT0,
`a`VARCHAR(20) DEFAULTNULL,
`b`VARCHAR(10) DEFAULTNULL,
`c`VARCHAR(10) DEFAULTNULL,
`d`VARCHAR(10) DEFAULTNULL,
PRIMARY KEY (`id`),
KEY`a_b`(`a`,`b`)
) ENGINE=INNODBDEFAULTCHARSET=utf81.2.3.4.5.6.7.8.9.
插入几条数据
复制
INSERT INTO test_temp VALUES(100, 10, 20, 2, 1);
INSERT INTO test_temp VALUES(200, 10, 40, 4, 2);
INSERT INTO test_temp VALUES(300, 10, 30, 3, 3);
INSERT INTO test_temp VALUES(400, 40, 10, 1, 4);1.2.3.4.
如果我们执行下面的 SQL,是不能走索引下推的:
复制
EXPLAIN SELECT * FROM test_temp WHERE a BETWEEN 10 AND 50 AND b < 50;1.
执行计划如下:
图片
图片
而如果我们把 SQL 改成:
复制
EXPLAIN SELECT * FROM test_temp WHERE a >= 10 AND a <= 50 AND b < 50;1.
执行计划如下:
图片
图片
可以看到,用上了索引下推。
3 总结
本文介绍了 BETWEEN AND 语句的用法、原理和性能,希望对你理解这个范围查询语句有所帮助 。