DATE(created_at)分组会变慢,因为函数导致索引失效,优化器被迫全表扫描并逐行计算日期;应改用时间范围过滤(如created_at >= '2026-07-21' AND created_at < '2026-07-22')配合GROUP BY,以命中索引提升性能。

直接用 DATE() 做 GROUP BY 会强制全表扫描,根本原因是函数使索引失效,不是语法错,是执行计划崩了。
为什么 DATE(created_at) 分组会变慢
MySQL 和 PostgreSQL 都无法对 DATE(created_at) 这类函数结果直接利用 created_at 上的索引。优化器看到函数,就放弃走索引路径,转而全表扫描每行、逐行计算日期值,再分组——数据量一过百万,耗时直线上升。
- 哪怕
created_at字段有 B-tree 索引,GROUP BY DATE(created_at)也完全用不上 - 分组键变成字符串形式的日期(如
'2026-07-21'),内部还要做类型转换和哈希计算 - 如果配合
HAVING或多层嵌套,执行计划更容易退化成临时表 + 文件排序
用时间范围替代 DATE() 函数
把“按天分组”转化为“按时间区间过滤”,让 WHERE 条件命中索引,再聚合,效率提升最稳。
- 不要写:
GROUP BY DATE(created_at) - 改写为:先确定日期范围,用
created_at >= '2026-07-21' AND created_at 等条件提前过滤 - 若需多天统计,用
UNION ALL拆成多个带索引的单日查询,比单条函数分组快得多 - PostgreSQL 可结合
generate_series()构造日期桶,再 LEFT JOIN,避免全表扫
用 created_at::date(PostgreSQL)或 DATE(created_at) 加函数索引
仅当必须保留函数写法时才考虑——但必须配对应索引,否则毫无意义。
- PostgreSQL:执行
CREATE INDEX idx_orders_date ON orders ((created_at::date));,注意括号写法,这是函数索引 - MySQL 不支持函数索引(8.0+ 支持,但只限于存储生成列),更推荐走范围方式
- 函数索引不会自动被优化器选用,务必用
EXPLAIN验证是否生效;出现Index Scan using idx_orders_date才算成功 - 函数索引增大写开销,且需定期
VACUUM(PG)或ANALYZE更新统计信息
真正要警惕的隐形陷阱
很多人以为加了索引就万事大吉,但实际中容易忽略三点:
- WHERE 条件里混用
DATE(created_at) = '2026-07-21'—— 这依然走不了索引,得改成范围写法 - 视图里封装了
DATE(created_at)分组,外层再加 WHERE,优化器大概率无法下推,导致先全量聚合再过滤 - 分区表按
created_at分区,但查询用DATE(created_at),分区裁剪会失效

















