优先用 hour(log_time) 提取小时,但需确保 log_time 为 datetime/timestamp 类型;若为字符串,先用 str_to_date 转换再取小时,并避免在 group by 中重复调用转换函数,同时为 where + group by 场景建立复合索引。

用 HOUR() 或 DATE_FORMAT() 提取小时,但要注意时区和数据类型
直接对 datetime 字段用 HOUR(log_time) 最快,但前提是字段是 DATETIME 或 TIMESTAMP 类型;如果存的是字符串(比如 '2024-05-20 14:23:17'),MySQL 会隐式转换,但可能触发全表扫描。更稳妥的是先确认类型:DESCRIBE access_log;。若为字符串,优先用 STR_TO_DATE(log_time, '%Y-%m-%d %H:%i:%s') 转一次,再取小时——别在 GROUP BY 里反复调用转换函数,否则无法走索引。
建复合索引加速 WHERE + GROUP BY 场景
海量日志统计慢,90% 是没索引或索引失效。例如查「昨天每小时 UV」:SELECT HOUR(log_time), COUNT(DISTINCT user_id) FROM access_log WHERE log_time >= '2024-05-19 00:00:00' AND log_time 这条语句需要覆盖 <code>log_time 和 user_id。建索引时顺序很重要:CREATE INDEX idx_time_uid ON access_log (log_time, user_id);。注意:HOUR(log_time) 是函数,不能直接被索引利用,所以过滤必须靠原始 log_time 的范围查询来驱动索引扫描。
避免 COUNT(DISTINCT) 在大结果集上 OOM
按小时分组后每组几百万用户?COUNT(DISTINCT user_id) 可能吃光内存,尤其在低配 MySQL 实例上。可考虑降级方案:
- 用 HyperLogLog 近似去重(MySQL 8.0+ 支持
APPROX_COUNT_DISTINCT(user_id)) - 预计算:每天凌晨跑一个任务,把每小时的
user_id写入临时表并建唯一索引,再统计 - 改用
GROUP BY log_date, HOUR(log_time), user_id加应用层聚合(适合离线分析)
APPROX_COUNT_DISTINCT 误差率通常
分区表不是银弹,按天分区才真正有用
有人一上来就想给访问日志表加按小时分区,这是陷阱。PARTITION BY RANGE COLUMNS(log_time) 按小时分会导致几百个分区,DDL 维护成本高,且查询跨多小时(比如「最近24小时」)反而更慢。正确做法是按天分区:PARTITION BY RANGE (TO_DAYS(log_time)),配合 WHERE log_time >= ? AND log_time 能快速裁剪分区。再配合前面说的复合索引,单日数据量超千万也扛得住。
真正卡住性能的,往往不是分组逻辑本身,而是没意识到 HOUR() 不走索引、COUNT(DISTINCT) 的内存爆炸风险,以及分区粒度错配。先跑 EXPLAIN 看执行计划,比调参有用得多。











