最稳妥的按天分组统计用DATE()函数,如GROUP BY DATE(created_at),它可走索引;避免用DATE_FORMAT()导致索引失效;按小时统计推荐DATE_FORMAT(created_at, '%Y-%m-%d %H:00:00')或DATE(created_at) + INTERVAL HOUR(created_at) HOUR;WHERE条件中勿对时间字段用函数,应改用范围查询以利用索引。

MySQL中用DATE()和DATE_FORMAT()做按天分组
按天统计最常用也最稳妥的方式是用DATE()函数提取日期部分,它会自动忽略时间,且对索引友好。如果字段是DATETIME或TIMESTAMP类型,直接GROUP BY DATE(created_at)就能得到每天的聚合结果。
注意别用DATE_FORMAT(created_at, '%Y-%m-%d')替代——虽然结果一样,但该函数无法走索引,大数据量下性能明显下降。只有当需要兼容不同格式(比如补零、中文星期)时才考虑DATE_FORMAT(),且应确保该字段有函数索引(MySQL 8.0+)。
常见错误:把created_at直接GROUP BY created_at,结果每条记录都单独一行,根本不是“按天”。
按小时统计必须用DATE_FORMAT()或HOUR()+DATE()组合
HOUR(created_at)只能拿到小时数(0–23),没法区分不同日期的同一小时;单独用DATE_FORMAT(created_at, '%Y-%m-%d %H:00:00')能生成带小时的时间戳,适合展示,但要注意时区——如果数据库时区和业务时区不一致,统计结果会偏移。
推荐写法:DATE_FORMAT(created_at, '%Y-%m-%d %H:00:00') 或 DATE(created_at) + INTERVAL HOUR(created_at) HOUR。后者在某些旧版本MySQL里更稳定,避免DATE_FORMAT的隐式类型转换问题。
容易踩的坑:GROUP BY HOUR(created_at)会把所有日期的10点全归为一组,完全失去时间维度。
WHERE条件里别让函数包裹时间字段
想查“最近7天的数据”,写成WHERE DATE(created_at) >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)会导致全表扫描——因为DATE()作用于字段,索引失效。
正确做法是把函数移到右边:WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)。这样created_at字段能走索引,速度差一个数量级。
同理,按小时筛选也要避免DATE_FORMAT(created_at, '%Y-%m-%d %H') = '2024-06-01 14',改用范围查询:WHERE created_at >= '2024-06-01 14:00:00' AND created_at 。
时区和夏令时带来的隐性偏差
MySQL服务器时区(@@time_zone)、连接时区(time_zone变量)、以及存储值本身是否带时区信息,三者不一致时,DATE()和HOUR()返回的结果可能和业务预期不符。
最保险的做法是统一用UTC存时间,查询时再转本地时区:DATE(CONVERT_TZ(created_at, '+00:00', '+08:00'))。但要注意CONVERT_TZ()依赖系统时区表,没加载的话会返回NULL。
另一个现实问题:夏令时切换当天会出现23或25个小时,用INTERVAL 1 HOUR做滑动窗口统计时,那一整天的小时分组可能多出/少掉一条,得人工校验边界。


















