订阅更新

SQL 怎么计算同时在线人数峰值?开始和结束同一时刻怎么算?

面试问 262 约 4 分钟读完

本文目录
  1. 面试怎么回答
  2. 先明确是人数,还是连接数
  3. 开始和结束同刻,要先声明端点规则
  4. 把区间改写成增减事件
  5. 为什么不直接按秒或分钟展开?
  6. 一个典型场景:边界时刻凭空多了一个人
  7. 跨日范围必须带上起始基线
  8. 参考资料

面试怎么回答

我会把在线会话看成时间区间,先约定用左闭右开。开始时加一,结束时减一,同一时刻先汇总净变化,再按时间累计取最大值。这样不会把同刻退出和进入算成短暂重叠。若统计人数而非连接数,需先合并同一用户重叠会话;另外处理统计区间之前已在线的基线、未结束会话和异常区间,避免按分钟采样漏掉真正峰值。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

业务想知道直播间最高同时在线多少人。有人按分钟统计登录数,再取最大值;但一个人可能在线半小时,另一个人只停留十秒。登录发生次数,并不能直接回答“同一时刻有多少人还在”。

在线峰值是区间重叠问题:先定义端点语义,再合并同刻增减并累计;人数还要求同用户区间去重。

先明确是人数,还是连接数

同一个用户可能同时开两个设备,也可能断线重连产生重叠会话。直接统计会话区间重叠,得到的是连接数或会话数。如果要去重人数,应先把同一用户相互重叠的在线区间合并,再计算全局重叠数量。

合并区间不能只和上一行结束时间比较,嵌套区间需要考虑此前最大的结束时间。这个准备步骤决定了统计单位,不能在最后简单加个 DISTINCT 就解决所有重叠问题。

下面假设输入已经完成去重和必要合并,一行代表一段有效在线区间,开始、结束都非空且开始早于结束。

开始和结束同刻,要先声明端点规则

常用约定是左闭右开,即 [start_time, end_time)。开始时已经在线,结束时不再在线。用户 A 在 10:05 退出、B 在 10:05 进入,不应因为实现顺序不同而瞬间算成两人。

如果业务需要结束时刻也包含,就属于另一种区间语义,应按明确的时间精度设计。不能一段 SQL 用包含端点,另一张报表又用不包含端点,却直接比较峰值。

把区间改写成增减事件

每个开始点记 +1,每个结束点记 -1。先按时间点合并净变化,再按时间累计。下面是通用窗口 SQL,适用于 PostgreSQL 17;online_intervals 已按前面的约定清洗:

WITH changes AS (
  SELECT start_time AS ts, 1 AS delta
  FROM online_intervals
  UNION ALL
  SELECT end_time AS ts, -1 AS delta
  FROM online_intervals
), per_time AS (
  SELECT ts, SUM(delta) AS delta
  FROM changes
  GROUP BY ts
), running AS (
  SELECT ts,
         SUM(delta) OVER (
           ORDER BY ts
           ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
         ) AS online_count
  FROM per_time
)
SELECT COALESCE(MAX(online_count), 0) AS peak_online
FROM running;

同刻先合并非常重要。如果把 +1 先处理、-1 后处理,中间产生的数字可能只是代码顺序制造的峰值,并不对应任何真实时间区间。

关键关系图

每个时间点累计后的值,表示从这个时刻到下一个变化点之前的在线量。需要峰值持续时间时,可以再取下一变化时刻,计算这些片段的长度;不要只保留最大数字却丢掉出现时间。

为什么不直接按秒或分钟展开?

逐秒展开容易把一个长会话扩成大量行,成本随持续时间增长。只按分钟采样又可能错过短暂峰值,例如两次采样之间有大量用户进入并很快离开。

端点扫描只关心状态发生变化的时刻,规模主要随区间数量增长。它仍需要排序或聚合,但不必为每段会话生成每一秒的数据。若时间本身已被源端截成分钟,算法也无法恢复不存在的秒级精度。

一个典型场景:边界时刻凭空多了一个人

比如两段区间是 [10:00, 10:05) 和 [10:05, 10:10),实际峰值为一。某实现排序时优先处理进入,算出峰值二,最初怀疑埋点重复,却找不到相同会话 ID。

真正的问题是端点处理与业务语义不一致。原样例的进入和离开时刻完全错开,没有覆盖同刻事件。修复后把同刻变化先聚合,并增加相邻区间、嵌套区间、重复会话、零时长和空输入测试。

跨日范围必须带上起始基线

只取当天的登录事件,会漏掉前一天已经在线、当天仍未退出的人。统计 [T0, T1) 时,应选取与该范围相交的区间,裁剪到范围边界,再生成端点;或者先计算 T0 的在场基线,再累计后续变化。

未结束会话也要有明确截止时间,不能随便填一个很远的日期。心跳中断、客户端崩溃造成的假在线,应按会话协议另行清洗,并区分观测值和超时推断。

最后,人数不应为负,裁剪范围内的最终增减应能闭合;这些检查能发现缺少结束事件或重复生成端点,但不能替代源端在线定义。

参考资料

刷题进度保存在当前浏览器