应按固定长度时间窗口(如15分钟)对齐分组,而非直接按hour(time)分组;需为time字段建b-tree索引,必要时加联合索引;避免group_concat截断与性能问题,优先用子查询抽样。

按时间窗口分组统计流量,别直接用 GROUP BY HOUR(time)
直接对 HOUR(time) 分组会丢失跨天或跨小时的连续性,比如把 02:59 和 03:01 拆到两个组里,无法反映真实“时段负载”。实际要的是固定长度的时间切片(如每15分钟、每小时),而不是按自然小时硬切。
推荐用 DATE_SUB 或 FROM_UNIXTIME + UNIX_TIMESTAMP 对齐时间窗口。例如每15分钟一组:
SELECT FROM_UNIXTIME(UNIX_TIMESTAMP(time) DIV 900 * 900) AS window_start, COUNT(*) AS req_count FROM access_log WHERE time >= '2024-06-01' AND time
-
900是15分钟的秒数,可换成3600(1小时)、1800(30分钟)等 - 注意
time字段类型必须是DATETIME或TIMESTAMP,否则UNIX_TIMESTAMP()可能返回NULL - WHERE 条件务必加,否则全表扫描代价极高,尤其日志表动辄上亿行
高峰时段识别:别只看 COUNT(*),得结合响应时间和并发特征
单纯统计请求数可能掩盖真实瓶颈。比如某时段请求数中等,但平均响应时间从 50ms 涨到 800ms,说明后端已过载;或者短时突增大量连接但请求量不高(如健康检查风暴)。
- 加
AVG(resp_time)、MAX(resp_time)、COUNT(DISTINCT client_ip)辅助判断 - 如果表里有
status_code,建议过滤出status_code >= 500的比例,异常率突增往往比总量更早暴露问题 - 避免在 SELECT 中用
ROUND(AVG(resp_time), 2)——浮点运算慢,先用AVG(resp_time)再应用层四舍五入
性能陷阱:没建好索引,查询跑十分钟都出不来
这类查询几乎必然走 time 字段范围扫描,没索引就是灾难。
- 必须在
time字段建 B-tree 索引:CREATE INDEX idx_time ON access_log (time); - 如果经常按
time+status_code过滤,考虑联合索引:CREATE INDEX idx_time_status ON access_log (time, status_code); - MySQL 5.7+ 支持函数索引,但
FROM_UNIXTIME(UNIX_TIMESTAMP(time) DIV 900 * 900)这种表达式无法被索引加速,只能靠基础时间字段索引兜底 - 分区表有用但非万能:按天分区(
PARTITION BY RANGE (TO_DAYS(time)))能加快范围裁剪,但不会加速 GROUP BY 本身
导出高峰时段结果时,小心 GROUP_CONCAT 截断和内存溢出
有时想顺带查出该时段的典型 URL 或错误详情,容易想到 GROUP_CONCAT(url)。但默认长度只有 1024 字符,且每个 group 的拼接会吃大量 sort_buffer。
- 临时调大限制:
SET SESSION group_concat_max_len = 1000000; - 慎用
GROUP_CONCAT(DISTINCT url)——去重开销大,不如在应用层 dedupe - 真要抽样分析,改用子查询 +
LIMIT更稳:(SELECT url FROM access_log l2 WHERE l2.time BETWEEN window_start AND DATE_ADD(window_start, INTERVAL 15 MINUTE) ORDER BY resp_time DESC LIMIT 3)
时段统计本身不难,难的是让结果真正反映系统压力。时间对齐方式、指标组合、索引覆盖、结果抽取策略——漏掉任何一环,查出来的“高峰”可能只是假象。











