为什么 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 反而更容易被提前物化。优先用
>=和
替代 <code>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+ 树索引只能高效支持「最左前缀 + 等值匹配 + 最多一个范围匹配」的组合。一旦出现范围(
>、
、<code>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的理想路径。
