计算每个商品详情页的跳出率。定义为:用户进入该商品详情页后,未进行任何其他页面跳转(如加购、点击推荐、返回搜索等)便直接关闭了页面的会话占比
面试题:计算商品详情页的跳出率
1. 题目背景与定义
在电商数据分析中,商品详情页的跳出率是一个衡量商品吸引力及流量质量的核心指标。
本题定义:用户进入某商品详情页后,未进行任何其他页面跳转(如加购、点击推荐、返回搜索等)便直接关闭了页面的会话占比。
即:在一个会话(Session)中,如果用户访问了某商品的详情页,且该次访问是该会话的最后一次操作(后续无任何行为),则视为一次“跳出”。
跳出率计算公式:
2. 输入数据:用户行为日志表 user_behavior
| session_id (会话ID) | user_id (用户ID) | page_name (页面名称) | item_id (商品ID) | event_time (事件时间) |
|---|---|---|---|---|
| s01 | u01 | detail | 101 | 2023-10-25 10:00:00 |
| s01 | u01 | cart | 101 | 2023-10-25 10:01:00 |
| s02 | u02 | detail | 101 | 2023-10-25 10:05:00 |
| s03 | u03 | index | NULL | 2023-10-25 10:10:00 |
| s03 | u03 | detail | 101 | 2023-10-25 10:11:00 |
| s04 | u04 | detail | 102 | 2023-10-25 10:20:00 |
| s05 | u05 | detail | 102 | 2023-10-25 10:30:00 |
| s05 | u05 | detail | 101 | 2023-10-25 10:32:00 |
| s06 | u06 | detail | 101 | 2023-10-25 10:40:00 |
注:page_name 为 'detail' 表示商品详情页,此时 item_id 记录商品ID;其他页面如 'cart'(购物车)、'index'(首页)的 item_id 可能为具体值或 NULL。
3. 期望输出结果
| item_id (商品ID) | total_visits (总访问会话数) | bounce_visits (跳出会话数) | bounce_rate (跳出率) |
|---|---|---|---|
| 101 | 5 | 4 | 80.00% |
| 102 | 2 | 1 | 50.00% |
结果解析:
- 商品 101:在会话
s01,s02,s03,s05,s06中被访问过(共5个会话)。其中s02,s03,s05,s06中,101详情页是会话的最后一步(无后续行为,计为跳出);s01后面有 'cart' 行为(未跳出)。因此跳出率 = 4 / 5 = 80%。 - 商品 102:在会话
s04,s05中被访问过(共2个会话)。其中s04访问后无后续(跳出);s05访问 102 后跳转到了 101(未跳出)。因此跳出率 = 1 / 2 = 50%。
4. SparkSQL 核心解法
sql
WITH pv_with_next AS (
SELECT
session_id,
page_name,
item_id,
event_time,
-- 使用窗口函数获取当前会话中下一次事件的时间
LEAD(event_time) OVER (PARTITION BY session_id ORDER BY event_time) AS next_event_time
FROM user_behavior
)
SELECT
item_id,
COUNT(DISTINCT session_id) AS total_visits,
SUM(CASE WHEN next_event_time IS NULL THEN 1 ELSE 0 END) AS bounce_visits,
CONCAT(
ROUND(
SUM(CASE WHEN next_event_time IS NULL THEN 1 ELSE 0 END) * 100.0 / COUNT(DISTINCT session_id),
2
),
'%'
) AS bounce_rate
FROM pv_with_next
WHERE page_name = 'detail'
GROUP BY item_id;
5. 面试官视角:SparkSQL 深度分析与调优
在面试中,写出 SQL 只是第一步,能够深入分析执行原理、潜在瓶颈并给出调优方案,才是决定能否拿到 Offer 的关键。
分析一:窗口函数 LEAD 的物理执行过程
- Shuffle(洗牌阶段):
OVER (PARTITION BY session_id ORDER BY event_time)会触发 Spark 的 Shuffle 过程。Spark 会根据session_id的 Hash 值将数据重分区,确保相同session_id的所有数据流向同一个 Executor 的同一个 Task。 - Sort(排序阶段):在 Task 内部,数据会按照
event_time进行内存或磁盘排序。 - Scan(滑动窗口扫描):排序完成后,Spark 能够高效地通过指针移动,直接获取当前行的下一行(
LEAD)数据,避免了自连接(Self-Join)的高昂代价。
分析二:数据倾斜(Data Skew)风险与应对
在实际电商场景中,上述 SQL 面临巨大的数据倾斜风险:
- 问题起因:某些爬虫程序或公共账号可能产生极其庞大的
session_id(一个会话包含数十万条记录),导致 Shuffle 时特定 Task 负载过重,出现OOM(Out of Memory) 或严重的拖尾(Straggler)现象。 - 面试加分解决方案:
- 前置过滤:在
WITH子句前,过滤掉异常活跃的会话(如单会话 PV > 1000 的爬虫)。 - 减少参与窗口计算的数据量:如果只需要判断是否为最后一步,可以在做窗口计算前,先过滤掉明显不影响结果的日志(但此题需要看“下一次行为是否存在”,故前置过滤无用行为需谨慎)。
- 两阶段聚合(针对 Group By item_id 的倾斜):如果某个爆款商品的
item_id极其庞大,导致最后的GROUP BY item_id倾斜。可以先对item_id引入随机两阶段聚合,或者利用 Spark 的Adaptive Query Execution (AQE)自动倾斜合并功能。
- 前置过滤:在
分析三:替代方案对比(Window vs Group By)
如果不允许使用 LEAD 窗口函数,通常会采用 JOIN 方案:
- 先找出每个
session_id的MAX(event_time)作为会话终点。 - 再用原表与终点表进行
LEFT JOIN。
- 对比结论:Spark 官方推荐使用窗口函数。因为
JOIN方案需要两次扫描原表,并引入一次大表 Join(产生额外的 Shuffle),而LEAD窗口函数只需要一次 Shuffle 和一次线性扫描,效率明显优于JOIN方案。
右滑查看面试常问