日期范围查询慢通常因索引使用不当:对字段用date()等函数导致索引失效,应改用created_at >= '2026-08-12 00:00:00' and created_at
日期范围查询慢,大概率不是没建索引,而是索引建错了或用错了。 单纯给
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 temporaryIN 列表太大导致范围查询退化
当日期范围拆成几十个离散点(比如用
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 上。












