会话超时切分是按用户行为间空闲时间>X秒断开为多个逻辑会话;不能用GROUP BY user_id,因其丢失时间顺序与间隔判断能力,必须依赖LAG()计算时间差标记新会话起点,再用SUM()累计生成有序会话ID。

什么是会话超时切分,为什么不能用 GROUP BY
会话超时切分本质是把用户连续行为按「空闲时间 > X 秒」断开成多个逻辑会话。直接 GROUP BY user_id 会把所有记录压成一组,丢失时间顺序和间隔判断能力;必须依赖行间比较,而窗口函数(特别是 LAG() 和累计逻辑)是唯一可控的方案。
用 LAG() 计算上一行时间差并标记会话起点
核心思路:对每个用户按时间排序,用 LAG(event_time) 拿到前一次操作时间,再判断当前是否超过超时阈值(比如 1800 秒)。一旦超时,就认为是新会话开始。
- 必须先用
ORDER BY event_time确保窗口内时间有序,否则LAG()返回无意义 -
LAG()默认返回NULL(首行无前驱),需用COALESCE()或条件处理,否则event_time - LAG() > 1800结果为NULL,被当作FALSE - 示例逻辑:
SELECT user_id, event_time, CASE WHEN event_time - COALESCE(LAG(event_time) OVER (PARTITION BY user_id ORDER BY event_time), event_time) > 1800 THEN 1 ELSE 0 END AS is_new_session
用 SUM() OVER 累计生成会话 ID
is_new_session 是布尔标记,但真正需要的是可分组的会话编号。用 SUM(is_new_session) OVER (PARTITION BY user_id ORDER BY event_time) 就能实现“每遇到一个 1,累计值加 1”,天然形成递增会话 ID。
- 注意必须保留
ORDER BY event_time,否则累计顺序错乱,同一个会话可能被分到不同 ID - 该
SUM()是“从头累加”,不是“滑动窗口”,所以无需指定ROWS BETWEEN - 最终会话 ID 是从 0 开始(首行
is_new_session=0→ 累计=0),如需从 1 起始,外层套+1
完整语句与容易忽略的边界问题
组合起来就是标准解法,但实际跑起来常因数据质量翻车:
- 时间字段类型必须是 timestamp/datetime,若存为字符串(如
'2024-01-01 10:00:00'),event_time - LAG()在多数数据库中会报错或隐式转成天数,应先用TO_TIMESTAMP()或STR_TO_DATE()显式转换 - 同一秒内多条记录?
ORDER BY event_time不稳定,需追加二级排序(如ORDER BY event_time, event_id)保证窗口内顺序确定 - PostgreSQL 对
interval计算敏感,超时阈值要写成INTERVAL '1800 seconds';MySQL 则用TIMESTAMPDIFF(SECOND, ..., ...)更稳妥
最麻烦的其实是空闲跨天——比如用户晚上 23:59 操作一次,第二天 00:02 再操作,差 3 分钟但日期变了。这时单纯比秒数没问题,但若业务定义“跨日即新会话”,就得额外加 DATE(event_time) != DATE(LAG(...)) 判断。

















