日期范围查询慢通常因索引使用不当:对字段用DATE()等函数导致索引失效,应改用created_at >= '2026-08-12 00:00:00' AND created_at < '2026-08-13 00:00:00'。

日期范围查询慢,大概率不是没建索引,而是索引建错了或用错了。 单纯给 created_at 加个单列索引还不够,得看你怎么查、字段类型、时区、是否套函数——任何一个环节出问题,索引就形同虚设。
WHERE DATE(col) = '2026-08-12' 为什么走不了索引
这是最常见也最隐蔽的坑:对日期字段用函数(DATE()、YEAR()、DATE_FORMAT())会导致索引完全失效,MySQL 必须把每一行的值取出来计算后再比对。
- 错误写法:
WHERE DATE(created_at) = '2026-08-12'→type: ALL,全表扫描 - 正确写法:
WHERE created_at >= '2026-08-12 00:00:00' AND created_at - 注意:字符串字面量按当前会话时区解释,再转成 UTC 去匹配索引(
TIMESTAMP存的是 UTC);DATETIME则直接按字面量比较,无时区转换 - 如果必须动态算“今天”,别用
CURDATE()包裹字段,而要用:WHERE created_at >= CURDATE() AND created_at
复合索引中范围条件后面字段失效
比如你建了联合索引 INDEX(status, created_at, user_id),但查询是 WHERE status = 'paid' AND created_at BETWEEN '2026-07-01' AND '2026-07-31' ORDER BY user_id ——这时 user_id 根本不参与索引查找,ORDER BY 也会触发 Using filesort。
- 根本原因:
created_at是范围条件,它截断了后续字段的索引下推能力 - 检查手段:看
EXPLAIN的key_len,比如索引总长 8 字节,但key_len只有 5,说明只用了前两个字段 - 解法一(排序优先):把排序字段提前,建
INDEX(status, user_id, created_at),前提是user_id选择性够高 - 解法二(范围+排序都重要):改写为
WHERE status = 'paid' AND created_at >= '...' AND created_at ,让排序字段也在范围条件内
TIMESTAMP 字段加索引的三个实操细节
TIMESTAMP 加索引有效,但容易因时区和 NULL 值翻车。
- 别在字段上套任何函数:
FROM_UNIXTIME(created_at)、UNIX_TIMESTAMP(created_at)全部放弃索引 - 建索引时显式加
NOT NULL(如果业务允许):CREATE INDEX idx_created_at ON orders(created_at) WHERE created_at IS NOT NULL或先ALTER TABLE orders MODIFY created_at TIMESTAMP NOT NULL - 避免用
ALTER TABLE ADD INDEX不指定名字,否则删索引时得先查SHOW INDEX FROM orders找系统生成的随机名 - 验证是否生效:
EXPLAIN中type必须是range或ref,key显示你的索引名,且Extra不出现Using filesort或Using temporary
IN 列表太大导致范围查询退化
当日期范围拆成几十个离散点(比如用 IN 枚举某几天),MySQL 可能主动放弃索引,尤其当统计信息不准或优化器预估成本过高时。
- 现象:
WHERE created_at IN ('2026-08-01', '2026-08-02', ..., '2026-08-30')→type: ALL - 安全阈值:单列
IN值少于 20–30 个通常能走索引;超过建议改用临时表关联 - 替代方案:
CREATE TEMPORARY TABLE tmp_days (day DATE); INSERT INTO tmp_days VALUES (...); SELECT * FROM orders JOIN tmp_days ON DATE(orders.created_at) = tmp_days.day——但注意这里DATE()又失效了,所以更稳妥的是把日期范围转成BETWEEN合并 - 真正要拆分,应按时间连续性切片,比如每天一个
BETWEEN查询,再用UNION ALL合并结果集
最容易被忽略的是:即使加了索引、写了范围查询、也避开了函数,只要 SELECT * 回表开销大,或者 read_rnd_buffer_size 太小,MRR 优化就起不来——这时候 key_len 对了,但实际性能还是卡在磁盘 IO 上。


















