WHERE col BETWEEN a AND b 比 col >= a AND col

为什么 WHERE col BETWEEN a AND b 有时比 WHERE col >= a AND col 慢?
二者语义等价,但 MySQL 优化器对 BETWEEN 的常量推导更保守,尤其当涉及函数或隐式类型转换时。例如 created_at BETWEEN '2024-01-01' AND NOW() 中 NOW() 是非确定性函数,可能导致索引失效;而显式写成 created_at >= '2024-01-01' AND created_at 反而更容易被提前物化。
- 优先用
>=和替代BETWEEN,尤其右侧边界含函数、子查询或变量 - 确保两端值类型一致:比如
id BETWEEN '1' AND '100'会触发字符串比较,若id是INT,应写成id BETWEEN 1 AND 100 - 避免在字段上套函数:
DATE(created_at) BETWEEN ...必然无法走索引,改用created_at >= '2024-01-01' AND created_at
复合索引中范围条件必须放最后吗?
是的,且这是最容易踩的坑。MySQL 的 B+ 树索引只能高效支持「最左前缀 + 等值匹配 + 最多一个范围匹配」的组合。一旦出现范围(>、、BETWEEN、LIKE 'abc%'),其右侧所有字段都无法用于索引查找。
- 对于查询
WHERE status = 1 AND created_at > '2024-01-01' AND user_id = 123,索引(status, created_at, user_id)只能用到前两个字段,user_id不参与索引查找 - 如果业务中
user_id过滤性更强,应调整为(status, user_id, created_at),把范围字段放在最后 -
IN列表不算严格意义上的“范围”,但 MySQL 8.0+ 对IN后多个常量会做等值展开,仍可继续使用后续字段 —— 但别依赖这个行为,IN超过几百项时性能会断崖下跌
如何判断范围查询是否真的走了索引?
光看 EXPLAIN 的 type 是 range 不够,得结合 key_len、rows 和实际执行时间验证。常见假象是:索引存在、type 显示 range,但 key_len 远小于预期,说明只用了索引前缀。
- 执行
EXPLAIN FORMAT=TREE(MySQL 8.0+)看索引扫描路径,比传统EXPLAIN更直观 - 检查
key_len:比如索引是(a INT, b VARCHAR(50)),若a为NULL,单列INT占 5 字节(4+1),b若为 utf8mb4 字符集且定义长度 50,则最多占 200 字节(50×4),加起来 205;若实际key_len=5,说明b完全没用上 - 用
SELECT * FROM t WHERE ... INTO DUMPFILE '/tmp/test'配合慢日志中的Rows_examined对比,确认是否真按索引行数扫描
时间范围查询分页深翻怎么不崩?
用 LIMIT offset, size 做深分页时,即使有索引,MySQL 仍要扫描 offset + size 行才能跳过前面的数据 —— offset 越大越慢。时间范围本身不能解决这个问题,但可以换思路。
- 放弃
OFFSET,改用游标分页:记录上一页最后一条的created_at和id,下一页查WHERE created_at >= ? AND id > ? ORDER BY created_at, id LIMIT 20 - 如果必须用时间范围分页,先用覆盖索引查出 ID 列表(如
SELECT id FROM t WHERE time BETWEEN ... ORDER BY time LIMIT 20000, 20),再用IN回表,比直接LIMIT 20000,20快得多 - 对实时性要求不高的场景,考虑预生成时间分区表(如按天/月分表),查某天数据时直接路由到对应表,避免大范围扫描
索引设计不是一锤子买卖,范围条件的位置、数据分布倾斜度、查询并发模式都会影响最终效果。上线前务必用生产级数据量压测,而不是只看 EXPLAIN 的理想路径。











