做数据库开发或者运维的同学,几乎每天都会碰到 BETWEEN。这个关键词看起来简单,但我自己在排查线上慢查询和诡异数据结果时,见过不少因为 BETWEEN 用错边界、用错类型而翻车的案例。这篇文章不打算讲那种特别高深的东西,就把 BETWEEN 从语法、边界、日期时间、字符串、性能这几个角度彻底捋一遍。它适合刚入门 SQL 的读者,也适合写了好几年 SQL 但没仔细想过边界细节的开发者参考。我会把我在实际项目中踩过的坑、排查过的慢 SQL、以及最终沉淀下来的写法习惯,一起放在里面,争取让你看完之后能直接用到自己的代码里。
1. BETWEEN 基础语法与边界规则
1.1 最小语法结构:闭区间范围查询
BETWEEN 在 SQL 标准里的定位非常明确:它是一种范围条件运算符,用于筛选某个字段的值落在指定区间内的记录。最基本的语法长这样:
sql复制SELECT *
FROM products
WHERE price BETWEEN 100 AND 200;
这句 SQL 的含义是,找出价格在 100 到 200 之间的商品。这里最核心、也最容易被忽略的一个规则是:BETWEEN ... AND ... 是一个闭区间,也就是说,端点值 100 和 200 本身都是包含在查询结果里的。
如果你把这个写法展开成普通条件判断,它等价于:
sql复制SELECT *
FROM products
WHERE price >= 100 AND price <= 200;
很多老开发会习惯性地把 BETWEEN 当成“大于等于 AND 小于等于”的语法糖,这个理解没有问题,但也正是因为这个等价关系,大家在排查问题时往往会忽略一个细节:BETWEEN 的两个端点值,在构造条件时并没有“排除端点”的能力。无论你想不想要端点,它都会把端点带上。
我在实际项目里就遇到过这样一个场景:前端页面有一个价格筛选,产品经理给的描述是“价格在 100 到 200 之间”,但产品逻辑里其实希望的是“价格大于 100 且小于 200”,也就是不包含刚好 100 和刚好 200 的商品。当时开发直接用 BETWEEN 100 AND 200 写了,结果商品价格为 100 和 200 的记录全都出现在筛选结果里,用户在页面上看到“低于 100 元的商品”出现,觉得很奇怪。这类需求偏差,其实不是 BETWEEN 的问题,而是我们对范围边界的理解没有同步对齐。
1.2 和 IN、比较运算符放在一起看,才能理解它到底解决什么问题
BETWEEN 和 IN 看起来都是筛选多个值,但其实它们解决的场景完全不同。IN 用于离散的、没有连续关系的值集合,比如查指定几个分类:
sql复制SELECT *
FROM products
WHERE category_id IN (1, 3, 5);
而 BETWEEN 用于连续的、有大小顺序的范围区间。如果值的分布是连续的,用 BETWEEN 明显比写一串 OR 要清晰:
sql复制-- 不推荐的写法
SELECT *
FROM products
WHERE price >= 100 OR price <= 200;
等一下,这个写法其实是错的,因为 OR 会把所有价格小于等于 200 的记录都选出来,同时把大于等于 100 的记录也选出来,结果就是个并集,逻辑上完全不对。正确的非 BETWEEN 写法应该是 price >= 100 AND price <= 200。我见到很多初学者会把 BETWEEN 和 OR 搞混,这其实说明了一个更底层的问题:BETWEEN 本质上是一个“与”逻辑,而不是“或”逻辑。
从可读性的角度讲,BETWEEN 的优势非常明显。连续范围的条件,如果项目里统一用 BETWEEN,代码扫一眼就知道查询意图,不需要在 >=、<=、AND 之间反复确认。但如果条件本身涉及“大于某个值”和“小于等于某个值”的不对称边界,那我建议不要硬套 BETWEEN,直接用比较运算符更严谨。因为 BETWEEN 只能表达对称的闭区间,无法表达“大于 A 且小于等于 B”这类边界不对称的需求。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 日期时间判断:BETWEEN 最容易踩坑的场景
2.1 datetime 精度带来的边界遗漏
如果说数字范围的 BETWEEN 还算温和,那日期时间字段上的 BETWEEN 简直是重灾区。我排查过很多次“查询结果少了一条数据”的故障,最后的根因几乎都出在 datetime 类型的精度问题上。
先看这个例子:
sql复制SELECT *
FROM orders
WHERE order_time BETWEEN '2025-01-01 00:00:00' AND '2025-01-01 23:59:59';
这段 SQL 本意是查 2025 年 1 月 1 日全天的订单。表面上看,23:59:59 作为当天最后一秒,已经覆盖了绝大部分情况。但问题在于,如果 order_time 字段是 datetime 类型,并且数据库存储的精度支持毫秒甚至微秒,那么一单发生在 2025-01-01 23:59:59.500 的订单就不会被查出来。这个时间段正好落在 23:59:59 和 2025-01-02 00:00:00 之间,而你的查询条件只截到了秒,那半秒钟的订单就凭空消失了。
在 SQL Server 里,传统 datetime 类型的精度大约是 3.33 毫秒,也就是 2025-01-01 23:59:59.997 是合法的值,它大于 23:59:59,所以会被 BETWEEN 排除。而 MySQL 的 datetime 默认精度是秒,但如果你在定义表结构时使用了 datetime(3) 或者 datetime(6),同样会出现这个问题。
我见过一个比较有意思的排查过程:某天业务方反馈后台报表里少了一笔订单,我查了当天的全部订单记录,怎么查都差一笔。后来把时间范围放宽到第二天的凌晨,才在第二天的数据里找到了那笔订单。它的订单时间正好是 23:59:59.997。从业务的角度讲,这笔订单确实发生在当天,但是从 SQL 条件的角度讲,它不在 BETWEEN '2025-01-01 23:59:59' 的范围里。
2.2 避开时间部分:推荐写法是 >= 加上小于下一天
后来我在团队里定了一个规矩:凡是查“某一天”的数据,一律不要用 BETWEEN 时间去包这一天。更好的写法是用 >= 和 <,把结束时间设置为“下一天的零点”:
sql复制SELECT *
FROM orders
WHERE order_time >= '2025-01-01 00:00:00'
AND order_time < '2025-01-02 00:00:00';
这个写法的核心思想是左闭右开区间:包含起始时间点,但不包含结束时间点。只要你把结束时间写成下一天的零点,所有当天发生的时间都能被覆盖到,即使是 23:59:59.999999 也不会漏。
更进一步的简化写法是直接使用日期字符串,让数据库做隐式转换:
sql复制SELECT *
FROM orders
WHERE order_time >= '2025-01-01'
AND order_time < '2025-01-02';
在主流数据库中,'2025-01-01' 会被自动转换为 2025-01-01 00:00:00,所以这个写法是成立且安全的。我也见过有人写 order_time BETWEEN '2025-01-01' AND '2025-01-02',然后以为只查了一天,实际结果却包含了第二天的凌晨数据。因为 BETWEEN 是闭区间,'2025-01-02' 会被转换为 2025-01-02 00:00:00,刚好把第二天一开始的订单也查了出来。这个坑真的是防不胜防,特别是当表的记录没有集中在整点附近时,几乎不会被立刻发现。
3. 字符串比较:BETWEEN 的字典序陷阱
3.1 字符串 BETWEEN 不是按长度比较,而是按字符顺序
数字和日期之外,把 BETWEEN 用在字符串字段上,是我见过最容易让开发者懵圈的场景。很多人下意识地以为 BETWEEN 'a' AND 'z' 就是“从 a 到 z 所有拼音开头或者字母开头的字符串”,但实际上,字符串比较是按字典序(也就是字符编码顺序)进行的。
举个例子:
sql复制SELECT *
FROM users
WHERE username BETWEEN 'a' AND 'z';
这段 SQL 并不是想当然的那样返回所有 username 首字母在 a 到 z 之间的用户。字典序比较是从第一个字符开始逐个比较的,所以 'abc' 确实在 'a' 和 'z' 之间,但 'ab' 也在,'aaron' 也在。至于 'A' 是否在 'a' 和 'z' 之间,就要看数据库的排序规则是否区分大小写。
更极端的例子是字符串数字混合的情况:
sql复制SELECT '10' BETWEEN '2' AND '9'; -- 结果是 true
这个结果让很多人抓狂。原因在于字符串比较不是数值比较,它逐字符比较 '10' 和 '2':第一位 '1' 比 '2' 小,所以 '10' 在字典序上排在 '2' 前面,自然就落在了 '2' 和 '9' 组成的区间之外……等一下,我重新想一下。
其实 '10' BETWEEN '2' AND '9' 的结果确实是 true 还是 false?这里要注意:比较的是 '10' 和 '2'、'9'。逐字符比较时,'1' 小于 '2',所以 '10' < '2',这意味着 '10' 不在 '2' 和 '9' 之间,结果应该是 false。但如果你用的是 '5','5' 大于 '2' 且小于 '9',结果为 true。总之,字符串数值比较和纯数值比较完全不同,如果你字段里存的是字符串类型的编号,比如订单号 '1001'、'2003',那用 BETWEEN 筛选范围时,一定要先确认这些编号的位数一致。如果位数不一致,BETWEEN '100' AND '200' 这种查询就会得到完全不符合预期的结果,因为 '99' 会被认为大于 '100'。
3.2 排序规则与字符集的影响
字符串比较还受到数据库排序规则的影响。在 SQL Server 里常见的是 CI(大小写不敏感)和 CS(大小写敏感)的区别。默认的 CI 排序规则下,BETWEEN 'a' AND 'z' 会把 'A'、'B' 等大写字母也包含进来,因为排序时不区分大小写。而在 MySQL 里,utf8mb4 字符集下默认的 utf8mb4_general_ci 同样是大小写不敏感的。这就导致同样一段 SQL,在本地开发库和线上生产库的排序规则不一致时,查询结果可能出现差异。我建议在使用字符串 BETWEEN 之前,先确认一下表的 collation 设置,不要想当然。
中文排序又是另一个坑。在大多数数据库中,中文在排序规则下并不是按拼音顺序排列的,而是按字符的 Unicode 编码顺序。所以在 SQL Server 的默认排序规则下,BETWEEN '张' AND '赵' 并不一定能查到你想要的那些姓氏,因为 '刘'、'李' 这些字在 Unicode 编码中的位置和你日常的拼音习惯对不上。如果业务上需要按拼音首字母筛选,就应该在应用层做好拼音转换,或者引入专门的搜索解决方案,而不是依赖 BETWEEN 去处理中文范围。我在实际项目里就见过一次用 BETWEEN 查姓氏范围的需求,最后被产品经理吐槽结果完全不对,后来改成前端直接按拼音首字母筛选了。
4. 执行效率:BETWEEN 与索引、隐式转换
4.1 BETWEEN 可以走索引,但有几个前提
从执行计划的角度看,BETWEEN 在优化器眼里通常会被转换成范围扫描(range scan),所以只要字段上有合适的索引,它一般都能高效地利用索引。这是 BETWEEN 对比 OR 条件的一大优势。如果写成下面这种形式:
sql复制SELECT *
FROM orders
WHERE order_time BETWEEN '2025-01-01' AND '2025-01-31';
数据库可能在 order_time 字段上走索引范围扫描。但如果写成:
sql复制SELECT *
FROM orders
WHERE order_time >= '2025-01-01' OR order_time <= '2025-01-31';
那不仅是逻辑问题,优化器还很可能把查询改成全表扫描,因为 OR 条件很难直接转换为一个连续的索引范围。
不过,这里有一个必须注意的前提:字段上的任何函数运算都会导致索引失效。比如:
sql复制SELECT *
FROM orders
WHERE DATE(order_time) BETWEEN '2025-01-01' AND '2025-01-31';
因为在 order_time 上套了 DATE() 函数,索引就无法直接用于这个条件,最终大概率是全表扫描。这个坑在 MySQL 里特别常见。正确的处理方式是在条件一侧直接使用原始字段,配合日期字符串或者显式转换后的常量,把函数运算放到常量这边。
另外还需要注意隐式类型转换的问题。如果某个字段是 varchar 类型,里面存的是数字字符串,你用 WHERE code BETWEEN 100 AND 200 去查,数据库可能会把 varchar 列转换为数值类型再做比较,一旦对列本身做转换,索引就会失效。我建议在写条件之前,先检查一下字段类型和条件值的类型是否一致,如果存在类型不匹配,优先修正应用传入的参数类型,而不是依赖数据库的隐式转换。
4.2 配合慢 SQL 优化的一点实际经验
慢 SQL 优化是一个很大的话题,但 BETWEEN 相关的慢查询通常逃不过下面几个原因:一是条件字段没有索引;二是字段被函数包裹或者发生隐式转换;三是范围过大,导致优化器认为全表扫描比走索引更划算。
我在排查一个线上慢 SQL 时遇到过这样的案例:订单表里有一个 created_at 字段,业务方每个月会拉一次上月的数据。原 SQL 写的是:
sql复制SELECT *
FROM orders
WHERE DATE(created_at) BETWEEN '2025-06-01' AND '2025-06-30';
这个查询在几百万行的表上跑了 4 秒多,原因是 DATE(created_at) 导致无法使用索引。我把写法改成:
sql复制SELECT *
FROM orders
WHERE created_at >= '2025-06-01 00:00:00'
AND created_at < '2025-07-01 00:00:00';
查询时间直接降到了 20 毫秒左右。这个区别是数量级的差距,也让我意识到一个道理:BETWEEN 本身不慢,慢的是让索引失效的写法。同样是范围查询,能用字段原始值比较的情况,就尽量不要动列本身。
这里我想多说一句关于“范围过大”的判断。当你用 BETWEEN 查询的范围占到表数据的很大比例时,优化器可能会选择直接扫描全表而不是走索引,这其实是正常的,不一定代表 SQL 有问题。但如果这种大范围查询频繁执行,可能需要考虑从业务层面拆分查询维度,或者加一层汇总表来减少实时扫描的数据量。
5. 常见问题速查与排查实录
5.1 五个高频问题排查
我把日常答疑里最常遇到的 BETWEEN 相关问题整理成一张速查表,方便你以后排查时对照。
| 问题现象 | 可能原因 | 排查方向 | 推荐写法 |
|---|---|---|---|
| 日期查询少了边界数据 | datetime 包含毫秒/微秒,边界没覆盖全 | 查看字段的精度和实际存储值 | 用 >= 起始日 AND < 次日零点 |
| 日期查询包含了第二天数据 | BETWEEN 是闭区间,第二天零点被包含 |
检查结束值是否用了当天日期而不是次日 | 改为左闭右开区间 |
| 字符串范围查询结果不符合预期 | 字典序与数值序不同,或排序规则大小写敏感度不符 | 检查排序规则和字符集 | 明确字段类型和预期排序规则 |
BETWEEN 查询走全表扫描 |
字段被函数包裹或存在隐式类型转换 | 查看执行计划,确认条件字段有没有被加工 | 对字段原始值直接比较 |
| 包含 NULL 的行没被查出来 | SQL 标准中 NULL 不参与常规比较 | 确认是否需要 IS NULL 单独处理 |
额外加 OR field IS NULL |
其中 NULL 的问题值得单独说一句。BETWEEN 是条件表达式,它的判断逻辑是“如果比较结果为 unknown 则不返回该行”。字段值为 NULL 时,NULL >= 100 AND NULL <= 200 的结果是 unknown 而不是 true,所以这类行永远不会出现在 BETWEEN 的查询结果里。这在大部分业务场景下是符合预期的,但如果你的业务逻辑里,NULL 需要被当成某个默认值参与范围判断,就必须在 SQL 里显式处理。比如:
sql复制SELECT *
FROM products
WHERE (price BETWEEN 100 AND 200)
OR price IS NULL;
5.2 不同数据库的实现差异
BETWEEN 在 SQL 标准中是通用的,但不同数据库在细节上还是有一些差异。我整理了几个常见数据库的行为对比,方便你做跨库开发时参考。
| 数据库 | 日期边界处理 | 字符串排序规则 | 注意事项 |
|---|---|---|---|
| MySQL | datetime 默认精度到秒,但支持 datetime(6) |
依赖 collation,常用 utf8mb4_general_ci |
函数包列会导致索引失效 |
| SQL Server | 传统 datetime 精度约 3.33 毫秒,datetime2 精度更高 |
默认排序规则通常是 CI,不区分大小写 |
date 类型只有日期部分 |
| PostgreSQL | timestamp 精度高,建议用范围条件 |
依赖 collation,对大小写敏感度可配置 |
支持 BETWEEN SYMMETRIC 这种特殊写法,能够自动处理端点顺序 |
| Oracle | DATE 类型包含时间部分,精确到秒 |
依赖数据库字符集和排序规则 | 需要特别关注“空字符串”会被当成 NULL |
PostgreSQL 的 BETWEEN SYMMETRIC 是一个比较有意思的特性:普通 BETWEEN 要求后边的第一个值小于等于第二个值,如果写反了,得到的结果就是空集。而 SYMMETRIC 会自动调整两个端点顺序,保证结果有值。例如:
sql复制SELECT *
FROM products
WHERE price BETWEEN SYMMETRIC 200 AND 100;
它会自动把条件视为 price BETWEEN 100 AND 200。这个特性在动态拼接条件的场景下能省掉不少判断逻辑,不过日常开发中还是建议在应用层先把端点排好序,这样 SQL 的可读性更高。
5.3 我自己的排查习惯
最后分享一个我自己的排查习惯。遇到 BETWEEN 相关的问题,我一般不会只看 SQL 本身,而是先看三样东西:第一,字段的真实类型和存储精度;第二,数据库的排序规则;第三,执行计划里条件字段有没有走索引。这三样确认完之后,基本能把问题范围缩小到很小的区域。然后再去翻数据,看边界值到底存的是什么格式,手动执行几组端点值的查询,就能很快定位到是写法问题还是数据问题。
我见过很多人一遇到 BETWEEN 查询结果不对,就猜是不是数据库有 bug,这种概率真的很低。更多时候是边界条件没想清楚,或者字段精度和数据格式没对齐。只要你能把一个范围查询拆成 >=、<、<= 这几个基本操作符,自己手动推演一遍边界,很多坑其实是可以提前规避的。
我个人在实际项目中,对 BETWEEN 已经形成一个比较固定的使用习惯:数字范围,确认闭区间语义符合需求就直接用;日期时间范围,一律倾向于 >= 起始日 AND < 次日;字符串范围,先确认排序规则和字典序行为,不确定时不硬用。这几个习惯帮我少踩了非常多的坑,数据准确性和查询性能都能兼顾。如果你刚接触 SQL,建议先多做几次边界测试,把数据库的行为摸清楚,再大规模用到业务代码里。
