1. 项目概述:BETWEEN 不是“范围查询”的快捷键,而是带陷阱的逻辑糖衣
你刚在写一条 SQL 查询,想查出订单金额在 100 到 500 元之间的所有记录,手一快敲出WHERE amount BETWEEN 100 AND 500——看起来天经地义。但上周我帮一个电商团队排查慢查询时发现,他们线上报表里“近30天销售额区间分布”这个指标,连续三天数据对不上。DBA 查了执行计划,索引明明建好了,amount字段也有 B-Tree 索引,可BETWEEN这条语句就是不走索引。最后定位到问题根源:他们把BETWEEN '2024-01-01' AND '2024-01-30'用在了created_at字段上,而该字段类型是DATETIME,但传入的字符串字面量没带时分秒,MySQL 在隐式转换时触发了全表扫描。这不是BETWEEN的 bug,而是我们对它底层逻辑的集体误读。
BETWEEN是 SQL 标准中定义的闭区间比较操作符,语法简洁,语义明确,但它绝不是>= AND <=的简单语法糖。它的行为高度依赖字段类型、字符集、时区设置、甚至数据库引擎的实现细节。我在过去十年带过的 27 个数据工程项目中,有 9 个出现过因BETWEEN使用不当导致的数据偏差、性能骤降或边界值漏查。最典型的是金融系统里“利率区间匹配”,开发同学用BETWEEN 3.85 AND 3.95查年化利率,结果 3.95% 的产品全部没被命中——因为数据库底层存储的是 DECIMAL(5,4),而输入字面量被解释为 DOUBLE,精度丢失后实际比较的是3.9499999999999997。这类问题不会报错,只会静默出错,等审计时才发现。
这篇文章不是教你“怎么写BETWEEN”,而是带你一层层剥开它的执行肌理:它在优化器眼里长什么样?为什么有时走索引有时不走?日期、字符串、数字三类场景下,边界值到底包不包含?如何写出既安全又高效的区间查询?适合谁看?如果你是刚学 SQL 的新人,本文会帮你避开教科书里从不提的坑;如果你是 DBA 或数据工程师,文中的执行计划对比、隐式转换日志、索引选择率计算,都是我在线上环境实测复现过的硬核细节;如果你负责数据质量,文末的“边界值校验 checklist”能直接嵌入你的数据稽核流程。全文所有结论,都来自 PostgreSQL 15、MySQL 8.0、SQL Server 2022 和 Oracle 21c 四大主流引擎的交叉验证。
1.1 核心需求解析:为什么“会写”不等于“写对”
很多人以为掌握BETWEEN就是记住语法:expr BETWEEN begin_expr AND end_expr。但真实业务场景远比语法复杂:
- 时间维度:查“今天订单”,是
BETWEEN '2024-06-15' AND '2024-06-15'还是BETWEEN '2024-06-15 00:00:00' AND '2024-06-15 23:59:59'?后者在毫秒级精度下必然漏掉23:59:59.123的记录; - 数值精度:银行利率表存的是
DECIMAL(6,4),前端传参是字符串"3.95",后端拼 SQL 时没做类型强转,数据库自动转成DOUBLE后比较,3.95 可能变成3.9499999999999997; - 字符串排序:查用户昵称
BETWEEN 'Alice' AND 'Bob',但数据库用utf8mb4_0900_as_cs排序规则,'alice'(小写)和'Álice'(带重音)是否包含?这取决于 collation 的二进制比较逻辑; - NULL 处理:
NULL BETWEEN 1 AND 10返回什么?不是FALSE,而是UNKNOWN,这直接影响WHERE子句的过滤结果。
这些都不是边缘 case。我在某支付公司做数据治理时,发现其核心对账表中“交易时间区间”字段有 12.7% 的记录因BETWEEN边界处理错误被错误归类。问题根源不是 SQL 写错了,而是没人去验证BETWEEN在当前数据库配置下的实际行为。所以本文的核心需求,不是教会你怎么打字,而是给你一套可验证、可审计、可复用的BETWEEN安全使用方法论。
1.2 技术影响范围:一个小操作符,牵动数据链路的五个关键节点
BETWEEN看似只是 WHERE 子句里的一个符号,但它像一根线,串起了从应用层到存储引擎的完整数据链路:
- 应用层参数绑定:Java MyBatis 的
#{}和${}对BETWEEN边界值的处理完全不同;Python SQLAlchemy 中between()方法默认启用inclusive参数,但手动拼接字符串时极易忽略; - SQL 解析器阶段:PostgreSQL 的
pg_parse_query()会将BETWEEN重写为>= AND <=,但 MySQL 的sql_yacc.yy语法分析器会保留原操作符,供优化器做特殊判断; - 查询优化器决策:SQL Server 的 Cardinality Estimator 会对
BETWEEN谓词单独建模,估算选择率时假设数据均匀分布,但现实数据常呈幂律分布,导致索引选择错误; - 执行引擎执行路径:Oracle 的 Cost-Based Optimizer 在
BETWEEN涉及函数索引(如UPPER(name))时,可能放弃使用索引,而改用全表扫描; - 存储引擎物理读取:InnoDB 的 B+Tree 索引遍历,
BETWEEN的起始和结束键决定了 leaf page 的扫描范围,一个边界值的小数点后位数差异,可能导致多读取 3 个 page。
这意味着,一个BETWEEN语句的性能与正确性,不是由某一行代码决定的,而是由整个技术栈协同决定的。我在某物流平台做慢 SQL 优化时,发现一条BETWEEN查询在测试库跑 200ms,在生产库跑 8s。最终定位到:测试库 MySQL 配置optimizer_switch='index_merge=on',而生产库关闭了该选项,导致优化器无法合并多个单列索引,被迫回表。这种跨层级的耦合,正是BETWEEN常被低估的复杂性所在。
2. 核心细节解析与实操要点:类型、边界、时区,一个都不能少
2.1 数值型字段:DECIMAL 与 FLOAT 的精度战争
数值型BETWEEN最常见的坑,是类型隐式转换引发的精度丢失。我们以一个真实案例切入:某基金销售系统需要筛选“年化收益率在 4.2% 到 4.8% 之间的理财产品”。表结构如下:
CREATE TABLE fund_products ( id BIGINT PRIMARY KEY, name VARCHAR(100), annual_rate DECIMAL(5,4) -- 精确到小数点后4位,如 4.2000, 4.8000 );开发同学写了这条查询:
SELECT * FROM fund_products WHERE annual_rate BETWEEN 4.2 AND 4.8;上线后,产品经理反馈:“4.8% 的产品一个都没查出来!” 我立刻在测试库复现:
-- 插入两条测试数据 INSERT INTO fund_products VALUES (1, 'A产品', 4.2000), (2, 'B产品', 4.8000); -- 执行查询 SELECT annual_rate, typeof(annual_rate) as type, CAST(4.2 AS DECIMAL(5,4)) as cast_42, 4.2 as literal_42 FROM fund_products;结果令人震惊:
| annual_rate | type | cast_42 | literal_42 |
|---|---|---|---|
| 4.2000 | decimal | 4.2000 | 4.2000000000000001776... |
4.2这个字面量,在 MySQL 中默认被解释为DOUBLE类型,其二进制表示无法精确存储十进制小数,实际值是4.200000000000000177635683940025...。而BETWEEN的底层逻辑是annual_rate >= 4.2 AND annual_rate <= 4.8,当annual_rate是DECIMAL(5,4)时,比较前会将4.2强制转为DECIMAL,但转换过程会四舍五入还是截断?这取决于 MySQL 的sql_mode。在默认STRICT_TRANS_TABLES下,CAST(4.2 AS DECIMAL(5,4))结果是4.2000,但BETWEEN的谓词重写发生在类型转换之前,优化器看到的是DECIMAL >= DOUBLE,触发隐式转换,将annual_rate转为DOUBLE再比较,此时4.8000转为DOUBLE后是4.7999999999999998223...,小于字面量4.8,导致4.8000不满足<= 4.8条件。
解决方案不是“加个 CAST”,而是从源头杜绝隐式转换:
强制参数类型声明(推荐):
SELECT * FROM fund_products WHERE annual_rate BETWEEN CAST('4.2' AS DECIMAL(5,4)) AND CAST('4.8' AS DECIMAL(5,4));应用层预处理(更安全):
- Java:用
BigDecimal.valueOf(4.2)构造参数,而非Double.valueOf(4.2); - Python:用
decimal.Decimal('4.2'),永远不用float(4.2); - 前端:输入框限制只能输数字,后端接收为字符串,再转
DECIMAL。
- Java:用
提示:
CAST('4.2' AS DECIMAL)比CAST(4.2 AS DECIMAL)安全十倍。前者从字符串解析,精度可控;后者从浮点数解析,精度已失。
2.2 日期时间字段:从“今天”到“本季度”的精确表达
日期BETWEEN是性能杀手的重灾区。根本原因在于:日期字面量的精度与字段类型的精度不匹配。我们以DATETIME字段为例:
CREATE TABLE orders ( id BIGINT PRIMARY KEY, order_time DATETIME(3), -- 精确到毫秒 amount DECIMAL(10,2) );要查“2024年6月15日全天的订单”,常见错误写法:
-- ❌ 错误:字符串字面量无精度,MySQL 自动补 '00:00:00' SELECT * FROM orders WHERE order_time BETWEEN '2024-06-15' AND '2024-06-15'; -- ❌ 更危险:用 '23:59:59',漏掉毫秒级记录 SELECT * FROM orders WHERE order_time BETWEEN '2024-06-15 00:00:00' AND '2024-06-15 23:59:59';第一条语句,'2024-06-15'被 MySQL 解释为'2024-06-15 00:00:00',BETWEEN变成order_time >= '2024-06-15 00:00:00' AND order_time <= '2024-06-15 00:00:00',只查出该秒内的记录。
第二条语句,'2024-06-15 23:59:59'是DATETIME,但字段是DATETIME(3),'23:59:59'实际被补为'23:59:59.000',而真实数据可能是'2024-06-15 23:59:59.123',它大于'23:59:59.000',因此被过滤掉。
正确姿势是“左闭右开”原则,用>= AND <替代BETWEEN:
-- ✅ 推荐:清晰、高效、无精度损失 SELECT * FROM orders WHERE order_time >= '2024-06-15 00:00:00' AND order_time < '2024-06-16 00:00:00'; -- ✅ 如果必须用 BETWEEN,显式指定毫秒精度 SELECT * FROM orders WHERE order_time BETWEEN '2024-06-15 00:00:00.000' AND '2024-06-15 23:59:59.999';为什么推荐>= AND <?因为:
- 它天然支持“下一个时间点”的计算,如
2024-06-16 00:00:00比2024-06-15 23:59:59.999更易计算且无歧义; - 所有主流数据库对
>= AND <的索引选择率估算更准确; - 避免了
BETWEEN在日期函数中的陷阱,如BETWEEN DATE_SUB(NOW(), INTERVAL 1 DAY) AND NOW(),NOW()每次调用值不同,可能导致优化器无法重用执行计划。
注意:
BETWEEN在涉及CURDATE(),NOW()等函数时,MySQL 会将其视为volatile表达式,禁止使用函数索引。而>= CURDATE() AND < DATE_ADD(CURDATE(), INTERVAL 1 DAY)可被优化器识别为常量范围。
2.3 字符串字段:排序规则(Collation)才是真正的裁判
字符串BETWEEN的行为,90% 取决于字段的COLLATION。同一个查询,在不同排序规则下结果可能天差地别。我们创建两个测试表:
-- 表1:使用 utf8mb4_0900_as_cs(大小写敏感,重音敏感) CREATE TABLE users_cs ( id INT PRIMARY KEY, name VARCHAR(50) COLLATE utf8mb4_0900_as_cs ); -- 表2:使用 utf8mb4_0900_ai_ci(大小写不敏感,重音不敏感) CREATE TABLE users_ci ( id INT PRIMARY KEY, name VARCHAR(50) COLLATE utf8mb4_0900_ai_ci ); INSERT INTO users_cs VALUES (1, 'Alice'), (2, 'alice'), (3, 'Álice'); INSERT INTO users_ci VALUES (1, 'Alice'), (2, 'alice'), (3, 'Álice');执行相同查询:
SELECT name FROM users_cs WHERE name BETWEEN 'Alice' AND 'Bob'; SELECT name FROM users_ci WHERE name BETWEEN 'Alice' AND 'Bob';结果:
| users_cs (cs) | users_ci (ci) |
|---|---|
| Alice | Alice |
| Álice | alice |
| Álice |
原因在于排序规则的二进制比较逻辑:
utf8mb4_0900_as_cs:'Álice'的 Unicode 码点 U+00C1 大于'Alice'的 U+0041,且小于'Bob'的 U+0042,所以包含;utf8mb4_0900_ai_ci:'alice'和'Alice'被视为相等,'Álice'与'Alice'也被视为相等(重音不敏感),所以BETWEEN的边界比较失效,实际执行的是name >= 'Alice' AND name <= 'Bob',但'alice'的归一化值等于'Alice',满足条件。
实操要点:
- 永远不要假设字符串
BETWEEN的结果。在建表时,明确指定COLLATION,并用SHOW FULL COLUMNS FROM table_name验证; - 对于需要精确字节比较的场景(如密码盐值、token),强制使用
_bin排序规则,如utf8mb4_bin; - 在应用层做字符串范围查询前,先用
SELECT COLLATION(name)确认字段排序规则,再决定是否用BETWEEN。
3. 实操过程与核心环节实现:从执行计划到线上压测的全链路验证
3.1 执行计划深度解读:如何一眼识破BETWEEN是否走索引
BETWEEN是否走索引,不能只看EXPLAIN输出的type字段。我总结了一套四步诊断法,已在 15 个线上环境验证有效。
步骤1:确认索引是否被选中
EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE order_time BETWEEN '2024-06-15 00:00:00' AND '2024-06-15 23:59:59';关键看execution_plan中的key字段。如果为NULL,说明索引未被选用。但注意:key非空不等于一定高效,还要看rows和filtered。
步骤2:分析索引选择率(Selectivity)filtered字段表示优化器预估的该条件过滤后的行数占比。如果filtered是100.00,说明优化器认为BETWEEN没过滤任何行,极可能放弃索引。真实案例:某日志表event_time字段有索引,但BETWEEN '2024-01-01' AND '2024-12-31'的filtered=100.00,因为优化器统计信息显示该字段 99% 的数据都在此范围内。
步骤3:检查索引覆盖度如果BETWEEN字段是复合索引的前导列,但查询还返回了非索引列,EXPLAIN的Extra字段会显示Using index condition(ICP)或Using where。ICP 表示存储引擎层用索引做过滤,效率高;Using where表示服务器层过滤,效率低。
步骤4:强制索引验证用FORCE INDEX强制走索引,对比执行时间:
-- 原始查询 SELECT COUNT(*) FROM orders WHERE order_time BETWEEN '2024-06-15' AND '2024-06-15'; -- 强制索引 SELECT COUNT(*) FROM orders FORCE INDEX (idx_order_time) WHERE order_time BETWEEN '2024-06-15' AND '2024-06-15';如果强制索引后快 10 倍,说明优化器的统计信息过期,需ANALYZE TABLE orders更新。
实操心得:我在线上环境发现,MySQL 8.0 的直方图(Histogram)功能对
BETWEEN优化帮助极大。开启后,ANALYZE TABLE orders UPDATE HISTOGRAM ON order_time WITH 16 BUCKETS;,优化器能更精准估算BETWEEN的选择率,避免误判。
3.2 线上压测与数据校验:用真实流量验证边界值
BETWEEN的最大风险是“静默错误”——不报错,但结果不对。必须建立自动化校验机制。
方案1:边界值快照比对对关键BETWEEN查询,每天凌晨用脚本抓取边界值附近的 10 条记录作为快照:
# Python 示例 def capture_boundary_snapshot(): # 查 2024-06-15 00:00:00.000 附近的数据 sql = """ SELECT id, order_time, amount FROM orders WHERE order_time >= '2024-06-15 00:00:00.000' AND order_time < '2024-06-15 00:00:00.100' ORDER BY order_time LIMIT 10 """ # 将结果存入校验表 insert_into_audit_table(sql_result)次日同一时间运行,比对快照是否一致。不一致则触发告警。
方案2:双写校验(Shadow Query)在应用层,对每条BETWEEN查询,同时执行两条语句:
-- 主查询(业务使用) SELECT * FROM orders WHERE order_time BETWEEN ? AND ?; -- 校验查询(仅记录日志) SELECT COUNT(*) as cnt_main FROM orders WHERE order_time BETWEEN ? AND ?; SELECT COUNT(*) as cnt_alt FROM orders WHERE order_time >= ? AND order_time < DATE_ADD(?, INTERVAL 1 SECOND);如果cnt_main != cnt_alt,说明BETWEEN边界处理异常,立即告警。
注意:
DATE_ADD(?, INTERVAL 1 SECOND)是为了适配秒级精度。如果是毫秒级,用DATE_ADD(?, INTERVAL 1 MICROSECOND)。关键是让校验查询的逻辑绝对清晰、无歧义。
3.3 工具链集成:将BETWEEN安全检查嵌入 CI/CD
我们把BETWEEN的安全检查做成了 Git Hook 和 SonarQube 规则,效果显著。
Git Pre-Commit Hook在.husky/pre-commit中加入:
# 检查 SQL 文件中是否有未加 CAST 的数字 BETWEEN if grep -r "BETWEEN [0-9]\+\.[0-9]\+ AND [0-9]\+\.[0-9]\+" *.sql; then echo "ERROR: Found unsafe numeric BETWEEN! Use CAST('x.x' AS DECIMAL) instead." exit 1 fi # 检查日期 BETWEEN 是否缺少精度 if grep -r "BETWEEN '[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}' AND '[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}'" *.sql; then echo "ERROR: Found date BETWEEN without time precision!" exit 1 fiSonarQube 自定义规则用 XPath 编写规则,检测 MyBatis XML 中的BETWEEN:
//text()[contains(., 'BETWEEN') and contains(., 'AND') and not(contains(., 'CAST'))]命中即标为Critical级别漏洞。
这套工具链上线后,团队BETWEEN相关 Bug 下降了 73%,平均修复时间从 4.2 小时缩短到 18 分钟。
4. 常见问题与排查技巧实录:那些让我熬夜到凌晨三点的坑
4.1 “明明走了索引,为什么还是慢?”——BETWEEN与索引合并的冲突
现象:一张订单表有idx_status和idx_time两个单列索引,WHERE status = 'paid' AND order_time BETWEEN ? AND ?执行很慢。EXPLAIN显示type=ref,key=idx_status,但rows=500000。
原因:MySQL 5.7+ 默认开启index_merge,但BETWEEN谓词在index_merge_intersection中支持有限。优化器尝试合并idx_status和idx_time,但BETWEEN的范围扫描代价高,最终放弃合并,只用了idx_status,导致order_time条件在服务器层过滤。
排查命令:
-- 查看优化器开关 SELECT @@optimizer_switch LIKE '%index_merge%'; -- 关闭 index_merge 测试 SET optimizer_switch='index_merge=off'; EXPLAIN ...; -- 再看执行计划解决方案:
- 创建复合索引:
ALTER TABLE orders ADD INDEX idx_status_time (status, order_time); - 或升级到 MySQL 8.0.20+,启用
use_index_extensions。
实操心得:
BETWEEN在复合索引中必须是后缀列。INDEX (order_time, status)对status = ? AND order_time BETWEEN ? AND ?无效,因为status不是前导列。
4.2 “NULL 值神秘消失”——BETWEEN与三值逻辑的博弈
现象:用户表有age字段允许 NULL,SELECT * FROM users WHERE age BETWEEN 18 AND 60返回结果中没有age IS NULL的记录,这正常。但开发同学误以为BETWEEN会自动排除 NULL,于是没加AND age IS NOT NULL,导致后续COUNT(*)统计时,NULL 值被计入总数,但BETWEEN查询结果里没有,数据对不上。
原理:NULL BETWEEN 18 AND 60的结果是UNKNOWN,而WHERE子句只接受TRUE,UNKNOWN和FALSE都被过滤。
验证 SQL:
SELECT NULL BETWEEN 18 AND 60 as result, CASE WHEN NULL BETWEEN 18 AND 60 THEN 'true' ELSE 'not true' END as case_result; -- result 为 NULL,case_result 为 'not true'安全写法:
-- 显式处理 NULL SELECT * FROM users WHERE (age BETWEEN 18 AND 60) OR age IS NULL; -- 或更推荐:业务层明确 NULL 含义,查询时排除 SELECT * FROM users WHERE age IS NOT NULL AND age BETWEEN 18 AND 60;4.3 “本地快,线上慢”—— 时区配置引发的血案
现象:开发环境SELECT * FROM events WHERE event_time BETWEEN '2024-06-15' AND '2024-06-15'0.1s,线上环境 12s。EXPLAIN显示线上走了全表扫描。
原因:开发库time_zone='+00:00',线上库time_zone='Asia/Shanghai'。'2024-06-15'字面量在解析时,MySQL 会根据time_zone转换为 UTC 时间。开发环境'2024-06-15'→'2024-06-15 00:00:00',线上环境'2024-06-15'→'2024-06-14 16:00:00'(UTC),导致BETWEEN范围错乱,优化器无法使用索引。
验证命令:
-- 查看时区 SELECT @@time_zone, @@system_time_zone; -- 查看字面量解析结果 SELECT CAST('2024-06-15' AS DATETIME) as parsed_time;终极方案:
- 所有时间字面量,强制带上时区:
'2024-06-15 00:00:00+08:00'; - 应用层统一使用 UTC 时间存储,查询时用
CONVERT_TZ()转换; - 数据库配置
default_time_zone='+00:00',杜绝隐式转换。
提示:
BETWEEN在TIMESTAMP字段上更危险,因为TIMESTAMP会自动时区转换,而DATETIME不会。线上库务必用DATETIME存储业务时间。
4.4 常见问题速查表
| 问题现象 | 根本原因 | 快速诊断命令 | 解决方案 |
|---|---|---|---|
BETWEEN不走索引,EXPLAIN显示type=ALL | 字段类型与字面量类型不匹配,触发隐式转换 | SHOW CREATE TABLE table_name;+SELECT typeof(?); | 统一使用CAST()强制类型,或应用层预处理 |
| 查询结果比预期少,边界值缺失 | 日期字面量精度不足,或BETWEEN右边界闭合导致上限截断 | SELECT MIN(order_time), MAX(order_time) FROM table WHERE ...; | 改用>= AND <左闭右开,或显式指定毫秒精度 |
| 同一查询在不同库结果不一致 | 排序规则(Collation)不同,或时区配置不同 | SELECT COLLATION(column_name) FROM information_schema.COLUMNS;SELECT @@time_zone; | 建表时显式声明COLLATE和TIME ZONE,CI/CD 中校验 |
BETWEEN在OR条件中失效 | 优化器对OR+BETWEEN的成本估算不准,放弃索引 | EXPLAIN FORMAT=JSON ...查看cost_info | 拆分为UNION ALL,或创建覆盖索引 |
COUNT(*)与BETWEEN查询结果行数不一致 | BETWEEN过滤了NULL,但COUNT(*)统计所有行 | SELECT COUNT(*), COUNT(column) FROM table; | 明确业务需求:是否需要包含NULL?并在WHERE中显式声明 |
5. 高级技巧与工程实践:让BETWEEN成为你的数据利器
5.1 动态区间生成:用窗口函数预计算边界
对于“每个用户最近3笔订单的金额区间”这类需求,传统写法是子查询套BETWEEN,性能极差。我们用窗口函数预计算:
-- 步骤1:为每个用户订单按时间排序 WITH ranked_orders AS ( SELECT user_id, amount, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_time DESC) as rn FROM orders ), -- 步骤2:取最近3笔,求 min/max user_ranges AS ( SELECT user_id, MIN(amount) as min_amount, MAX(amount) as max_amount FROM ranked_orders WHERE rn <= 3 GROUP BY user_id ) -- 步骤3:关联原表,用 BETWEEN 过滤(此时范围已确定) SELECT o.* FROM orders o JOIN user_ranges r ON o.user_id = r.user_id WHERE o.amount BETWEEN r.min_amount AND r.max_amount;关键点:BETWEEN的右值不再是变量,而是预计算的确定值,优化器能准确估算选择率。
5.2 索引优化实战:为BETWEEN设计专用索引
不是所有BETWEEN字段都适合建索引。我总结了三个黄金指标:
- 选择率(Selectivity) > 5%:如果
BETWEEN范围覆盖超过 95% 的数据,建索引反而降低写性能; - 数据分布偏斜度 < 0.3:用
SELECT STDDEV(amount)/AVG(amount) FROM orders;计算,值越小分布越均匀,BETWEEN效果越好; - 查询频率 > 100 次/小时:低频查询不值得为索引付出维护成本。
索引设计口诀:
- 单字段
BETWEEN:直接建单列索引; BETWEEN+ 等值查询:等值字段放前,BETWEEN字段放后,如WHERE status='paid' AND amount BETWEEN 100 AND 500→INDEX(status, amount);- 多个
BETWEEN:优先选选择率更低(范围更窄)的字段,如time BETWEEN ? AND ?通常比amount BETWEEN ? AND ?更窄。
5.3 数据质量监控:BETWEEN边界值自动稽核
我们在数据平台部署了BETWEEN边界值监控服务,每天自动执行:
-- 检查是否存在“刚好卡在边界”的数据 SELECT 'orders' as table_name, 'order_time' as column_name, MIN(order_time) as min_val, MAX(order_time) as max_val, COUNT(*) as total_count, SUM(CASE WHEN order_time = '2024-06-15 00:00:00' THEN 1 ELSE 0 END) as at_min_count, SUM(CASE WHEN order_time = '2024-06-