“每小时最高并发在线人数”指每小时内各瞬时在线人数的最大值,需将登录登出转为±1事件流,按时间排序累加得实时在线数,再按小时取最大值;须补全0–23点、处理登出缺失、建联合索引优化性能。

理解“每小时最高并发在线人数”的真实含义
这个指标不是简单按小时分组求 COUNT(*),而是要模拟用户会话的起止时间,在每个时间点上计算“当前有多少人在线”,再对每小时内的所有瞬时值取最大值。核心难点在于:用户登录和登出是离散事件,但“在线”是一个持续状态。
常见错误是直接用 GROUP BY HOUR(login_time) 统计登录人数——这统计的是“每小时新增人数”,不是“最高同时在线人数”。
用窗口函数 + 时间切片模拟实时在线数(MySQL 8.0+ / PostgreSQL)
思路是把每个用户的在线区间拆成两条事件:+1(登录)、-1(登出),按时间排序后累加,得到每个时刻的在线人数;再按小时分桶取最大值。
SELECT
HOUR(ts) AS hour_of_day,
MAX(online_cnt) AS max_concurrent_users
FROM (
SELECT
ts,
SUM(delta) OVER (ORDER BY ts, delta DESC) AS online_cnt
FROM (
SELECT login_time AS ts, 1 AS delta FROM user_sessions
UNION ALL
SELECT logout_time AS ts, -1 AS delta FROM user_sessions
) AS events
) AS timeline
GROUP BY HOUR(ts);
注意点:
-
logout_time必须存在且合理,缺失时可设为login_time + INTERVAL 30 MINUTE(根据业务设定超时) -
ORDER BY ts, delta DESC确保同一时间点先处理登录(+1)再处理登出(-1),避免瞬时归零误判 - 若数据库不支持窗口函数(如 MySQL 5.7),需改用自连接或存储过程,性能明显下降
用生成时间序列补全每小时粒度(避免漏掉空闲小时)
上面查询只返回有事件发生的小时。若想确保 0–23 点全部出现(比如展示完整折线图),需主动构造时间序列:
-- MySQL 8.0+ 示例:生成 0–23 小时 WITH hours AS ( SELECT 0 AS h UNION SELECT 1 UNION ... UNION SELECT 23 ) SELECT h AS hour_of_day, COALESCE(t.max_concurrent_users, 0) AS max_concurrent_users FROM hours LEFT JOIN ( -- 上面的主查询结果,别名为 t ) AS t ON hours.h = t.hour_of_day;
关键细节:
-
COALESCE把空值转为 0,表示该小时无任何用户在线 - 手动写
UNION24 次虽笨但兼容性强;PostgreSQL 可用GENERATE_SERIES(0,23) - 不建议用
DATE_SUB(NOW(), INTERVAL x HOUR)动态生成,容易因时区或跨日逻辑出错
性能与数据质量陷阱
这个统计对数据质量和索引很敏感:
-
login_time和logout_time必须有联合索引:INDEX(login_time, logout_time),否则子查询UNION ALL阶段可能全表扫描 - 若单日会话量超 10 万,窗口函数累加过程可能变慢,可考虑预计算每日峰值存入汇总表
- 最大并发数可能出现在两个小时交界处(如 13:59:59 登录 + 14:00:01 登出),此时它会被计入 13 点还是 14 点,取决于你用
HOUR(ts)还是HOUR(DATE_ADD(ts, INTERVAL 1 SECOND))—— 实际中统一按事件发生时刻的自然小时归属即可,不必强行“四舍五入”
真实业务里,最常被忽略的是登出时间缺失或被截断(比如 App 崩溃未上报 logout)。与其硬凑逻辑,不如先用 WHERE logout_time IS NOT NULL 明确过滤,再单独分析异常会话占比。

















