1|真实面经:Spark 参数怎么调?
来源:近期公开面经|字节跳动|数据开发与数据分析岗|面试时间 2026-03-31|难度:★★★★☆
牛客近期收录的这场面试明确记录了"Spark 参数怎么调",随后继续追问 Hive、Spark、数据湖等分布式架构共有的性能瓶颈。
核心考察点
面试官不是想听你背:
sql
executor-memory = 8G
executor-cores = 4
而是在看:
你是否会根据任务现象判断资源瓶颈。
标准回答
我会先判断瓶颈属于:
sql
CPU
Memory
Shuffle
GC
数据倾斜
IO
并行度
而不是直接修改参数。
例如任务大量:
sql
GC
Executor Lost
OOM
重点看:
spark.executor.memory
spark.executor.memoryOverhead
executor cores
缓存数据量
单 Task 数据量
如果 CPU 利用率低、Task 数又很少:
可能是并行度不足。
可以检查:
sql
spark.sql.shuffle.partitions
以及输入分区数。
如果 Task 数特别多、每个 Task 数据很小:
反而可能调度开销过大。
如果 Shuffle 特别大:
参数不是第一解决方案,应该先检查 Join、聚合、分区和数据倾斜。
面试口述
Spark 调优我一般不会从某个固定参数开始,而是先通过 Spark UI 判断瓶颈属于 CPU、内存、Shuffle、GC、数据倾斜还是并行度,再针对性调整 Executor 资源和 Shuffle Partition。比如大量 OOM 时我会检查单 Task 数据量、缓存和 Memory Overhead;如果大量小 Task,则减少不必要的分区;如果 Shuffle 特别大,我会优先优化执行逻辑而不是单纯增加资源。
追问
Executor Core 是不是越多越好?
sql
不是。单 Executor Core 太多意味着更多 Task 共用一个 JVM:
sql
GC压力
内存竞争
单Executor故障影响范围
都会增加。
2|SQL:找出连续登录 ≥ 3 天的用户
来源:自拟高频题|难度:★★★★☆
连续登录是今年公开 SQL 面经整理中仍反复出现的题型。
表:
sql
login_record(
user_id,
login_date
)
核心思路
非常经典:
sql
日期 - row_number。
先去重:
sql
WITH t AS (
SELECT DISTINCT user_id, login_date
FROM login_record
),
t2 AS (
SELECT
user_id,
login_date,
ROW_NUMBER() OVER(
PARTITION BY user_id
ORDER BY login_date
) AS rn
FROM t
),
t3 AS (
SELECT
user_id,
login_date,
DATE_SUB(login_date, INTERVAL rn DAY) AS grp
FROM t2
)
SELECT
user_id,
MIN(login_date) AS start_date,
MAX(login_date) AS end_date,
COUNT(*) AS consecutive_days
FROM t3
GROUP BY user_id, grp
HAVING COUNT(*) >= 3;
为什么 date - rn 能分组?
连续:
sql
date rn
09-01 1
09-02 2
09-03 3
分别减:
sql
09-01 - 1
09-02 - 2
09-03 - 3
得到相同日期。
所以连续区间天然形成同一组。
追问
同一天登录十次怎么办?
必须先:
sql
DISTINCT user_id, login_date
否则会破坏 row_number。
3|MySQL:覆盖索引是什么?为什么能减少回表?
来源:自拟高频题|难度:★★★☆☆
假设:
sql
CREATE INDEX idx_user_time
ON orders(user_id, create_time);
查询:
sql
SELECT user_id, create_time
FROM orders
WHERE user_id = 1001;
需要的数据:
sql
user_id
create_time
都已经存在于索引中。
因此:
可以直接从索引得到结果,而不必再通过主键访问聚簇索引取得整行。
这就是:
覆盖索引。
什么叫回表?
InnoDB 二级索引叶子节点通常保存:
sql
索引列
+
主键值
如果:
sql
SELECT user_id, create_time, amount
但 amount 不在这个二级索引里,就可能:
sql
二级索引
↓
取得主键
↓
聚簇索引
↓
取得 amount
这就是回表。
追问
是不是 SELECT 字段越少越好?
从 IO 和覆盖索引角度通常有帮助,所以避免无意义 SELECT * 是好习惯。
但:
不能为了覆盖索引无限往索引里塞字段。
因为索引本身也有存储和写入维护成本。
4|数仓:维度退化是什么?订单号为什么可能直接放事实表?
来源:自拟高频题|难度:★★★☆☆
事实表:
sql
order_fact
可能包含:
sql
order_id
user_key
product_key
date_key
amount
其中:
sql
user_key → 用户维表
product_key → 商品维表
但 order_id 呢?
如果订单号:
sql
没有大量需要单独维护的描述属性
主要用于事实查询和关联
没必要建立:
sql
dim_order
然后里面只有:
sql
order_id
这种意义很弱的维表。
于是直接把业务维度标识:
sql
order_id
保留在事实表中。
这就是:
sql
Degenerate Dimension,退化维度。
常见例子
sql
订单号
发票号
交易流水号
追问
退化维度是不是事实?
不是。
订单号本身不是:
sql
可加总的度量
它仍然是维度性质,只是:
没有独立维表。
5|MapReduce:Combiner 为什么不能随便用?
来源:自拟高频题|难度:★★★★☆
Map 输出:
sql
key → value
通常需要经过网络:
sql
Shuffle
发送到 Reducer。
Combiner 可以在 Map 端先做:
sql
局部聚合。
例如 WordCount:
sql
apple 1
apple 1
apple 1
先变成:
sql
apple 3
再传输。
这样明显减少 Shuffle。
但是为什么不能随便用?
因为 Combiner:
可能执行,也可能不执行,也可能执行多次。
所以业务逻辑必须满足:
局部聚合不会改变最终结果。
例如:
sql
SUM
MAX
MIN
通常容易满足。
但是平均值:
sql
AVG
不能简单:
sql
局部平均
↓
再平均
例如:
sql
组A:[1, 1]
平均 = 1
组B:[100]
平均 = 100
平均两个局部平均:
sql
(1 + 100) / 2 = 50.5
真实平均:
sql
102 / 3 = 34
错误。
正确做法可以传:
sql
(sum, count)
最后:
sql
total_sum / total_count
一句话
Combiner 是优化,不应该成为结果正确性的必要条件。
这句话值得背。
6|Hive:count(distinct user_id) 数据量巨大时为什么可能慢?
来源:自拟高频题|难度:★★★★☆
核心问题:
去重需要让相同 user_id 汇聚到一起。
数据规模大时可能产生:
sql
大量 Shuffle
网络 IO
Reducer 压力
热点 Key
尤其如果执行计划形成:
sql
单阶段重聚合
瓶颈可能非常明显。
怎么优化?
根据场景可以:
sql
先局部去重再全局统计
分组两阶段聚合
合理增加并行度
利用 Bitmap / HLL 等近似去重
预聚合
例如允许近似 UV 时:
HyperLogLog 往往比精确 distinct 更省空间。
追问
为什么不能永远用 HLL?
因为它是:
近似算法。
如果银行对账要求:
sql
精确人数 = 1,000,000
就不能随便接受概率误差。
7|Flink:Checkpoint 和 Savepoint 有什么区别?
来源:自拟高频题|难度:★★★★☆
两者都可以保存状态,但用途不同。
Checkpoint
主要用于:
自动故障恢复。
由 Flink 周期性触发:
sql
Job运行
↓
Checkpoint
↓
Checkpoint
↓
Checkpoint
Job 崩溃:
从最近成功的 Checkpoint 恢复。
生命周期更偏系统自动管理。
Savepoint
主要用于:
人工运维和版本迁移。
例如:
sql
停止旧Job
↓
创建Savepoint
↓
修改代码
↓
从Savepoint启动新Job
适合:
sql
升级
迁移
修改并行度
计划性停止
一句话
sql
Checkpoint → 故障恢复
Savepoint → 运维迁移
但底层都涉及:
Stateful computation snapshot。
追问
有 Checkpoint 就一定 Exactly-Once 吗?
不一定。
端到端 Exactly-Once 还要求:
sql
Source
State
Sink
整体配合。
如果 Sink:
sql
每次恢复后重复 INSERT
照样可能重复。
8|Kafka:为什么 Kafka 吞吐量这么高?
来源:自拟高频题|难度:★★★★☆
别只回答:
因为用了磁盘顺序写。
至少讲四层。
① 顺序写
Kafka 日志是:
sql
Append Only
顺序追加。
磁盘顺序 IO 比随机 IO 高效很多。
② Page Cache
Kafka 大量利用操作系统:
sql
Page Cache
减少 JVM 内存管理压力。
③ Zero Copy
发送数据时尽量减少:
sql
Kernel
User Space
Kernel
之间不必要的数据复制。
④ Batch
Producer 可以批量发送:
sql
message
message
message
↓
batch
减少:
sql
网络请求
系统调用
⑤ Partition 并行
多个 Partition:
可以由多个 Broker / Consumer 并行处理。
面试口述
Kafka 高吞吐并不是单一机制,而是顺序追加日志、Page Cache、批处理、减少数据复制以及 Partition 并行共同作用的结果。
9|Redis:RDB 和 AOF 怎么选?
来源:自拟高频题|难度:★★★★☆
RDB
周期性生成:
某一时刻的数据快照。
优点:
sql
文件紧凑
恢复速度较快
适合备份
风险:
两次快照之间宕机可能丢失一段数据。
AOF
记录:
数据修改操作。
例如:
sql
SET
INCR
DEL
可以配置不同刷盘策略。
通常:
数据持久性可以比低频 RDB 更强。
但文件和写入开销通常更高,因此存在:
sql
AOF Rewrite
等机制。
面试不要回答
RDB 快,AOF 安全,所以生产一定用 AOF。
真实生产会结合:
swift
恢复速度
可接受数据丢失窗口
性能
备份
Redis版本
高可用架构
综合决定。
近期 2026 面经整理里,Redis 持久化已经出现从"RDB/AOF 是什么"继续追到混合持久化、宕机后可能损失多少数据的趋势。
10|Linux:CPU 100%,你怎么定位?
来源:自拟场景题|难度:★★★★☆
不要第一句话:
重启服务器。
先:
sql
top
看:
sql
load average
CPU user/system/iowait
哪个进程占CPU
假设 PID:
sql
12345
进一步:
sql
top -H -p 12345
看:
哪个线程占 CPU。
还可以结合:
sql
ps
pidstat
vmstat
iostat
判断究竟是:
sql
CPU计算
IO等待
频繁上下文切换
如果是数据任务
再回到:
sql
Spark/Flink Task
数据倾斜
序列化
死循环
GC
并行度
排查。
一个重要区别
sql
CPU 100%
不一定意味着:
CPU 是问题。
例如大量 IO wait 时,系统表现慢但根因可能是磁盘。
所以一定看:
sql
us
sy
wa
等指标。
11|Doris / ClickHouse:为什么 OLAP 场景常使用预聚合?
来源:自拟高频题|难度:★★★☆☆
假设明细:
sql
每天 10 亿条交易
领导每天查询:
sql
SELECT province, SUM(amount)
FROM transactions
WHERE dt = ...
GROUP BY province;
每次扫描十亿条显然昂贵。
可以提前形成:
sql
province_daily_sales
例如:
sql
Beijing | 2026-09-08 | 10亿
Shanghai| 2026-09-08 | 8亿
查询直接:
sql
几百行
而不是十亿行。
这就是预聚合价值:
用存储和预计算换查询延迟。
但是为什么不能所有维度组合全部预聚合?
如果有:
sql
province
city
age
gender
channel
product
...
所有组合会产生:
维度组合爆炸。
所以需要根据:
sql
查询频率
业务指标
实时性
存储成本
选择聚合粒度。
12|结合你的项目:MySQL CDC 为什么比"每分钟 SELECT 一次"更适合实时链路?
来源:针对项目设计|难度:★★★★☆
假设不用 CDC。
Python 每分钟:
sql
SELECT *
FROM orders
WHERE update_time > last_time;
看起来也能做增量。
但存在几个问题。
① 时间边界
例如:
sql
last_time = 12:00:00
同一毫秒多条更新、事务提交顺序和时间精度处理不好,就可能:
sql
漏
或
重复
② 数据库压力
每分钟扫描:
sql
update_time > ?
意味着持续查询业务数据库。
CDC 则利用:
数据库已有的变更日志,例如 MySQL Binlog。
③ Delete
轮询表:
sql
SELECT ...
很难自然知道:
哪条数据刚被 DELETE。
CDC 日志可以表达:
sql
INSERT
UPDATE
DELETE
④ 低延迟
轮询天然存在:
sql
poll interval
CDC 可以更接近实时。
⑤ 顺序和位置
CDC 可以维护:
sql
Binlog File
Position
GTID
等进度。
因此更容易:
断点恢复。
追问
那 CDC 就绝对不会丢数据?
不能这么说。
还要考虑:
sql
Connector状态
Checkpoint
Offset
Binlog保留时间
Schema变化
Sink语义
所以 CDC 只是提供了更可靠的变更捕获基础。
13|场景题:银行 ODS → DWD 跑完了,但数据量比昨天突然少 40%,你发布吗?
来源:针对银行数仓场景设计|难度:★★★★★
答案首先是:
不能因为任务状态 SUCCESS 就直接发布。
调度成功只代表:
程序正常结束
不代表:
数据正确
第一步:判断是否业务真实变化
比较:
sql
今天记录数
昨天记录数
过去7/30天基线
上游源表记录数
如果昨天:
sql
1亿
今天:
sql
6000万
需要判断:
sql
节假日?
业务停机?
源系统切换?
部分机构没有上数?
第二步:分层对账
例如:
sql
源系统 1亿
↓
ODS 6000万
问题:
入仓。
如果:
sql
源系统 1亿
ODS 1亿
DWD 6000万
问题:
ODS → DWD。
继续按照:
sql
机构
日期
业务类型
分区
拆分。
例如发现:
sql
北京 100%
上海 100%
广东 0%
那问题瞬间从:
"少了 4000 万"
缩小成:
"广东分支数据没到。"
第三步:检查规则变化
尤其:
sql
WHERE条件
Join
Inner Join
字典映射
NULL
去重
例如:
sql
FROM transaction t
INNER JOIN dim_branch b
ON t.branch_code = b.branch_code
如果维表突然缺失大量 branch:
Inner Join 会把事实数据直接过滤掉。
第四步:阻断下游
如果数据异常:
sql
DWD
✕
DWS
✕
ADS
✕
驾驶舱
不要让错误数据继续传播。
第五步:修复 + 补数
修复后:
sql
指定 biz_date
↓
重跑异常分区
↓
数据量检查
↓
金额/笔数对账
↓
关键指标检查
↓
恢复下游
面试口述
我不会把任务成功等同于数据成功。出现 40% 的异常下降时,我会先判断是否存在真实业务原因,然后从源系统、ODS、DWD 逐层做数量和关键指标对账,并按机构、业务类型等维度拆分来定位第一个发生差异的位置。同时阻断依赖该数据的下游任务。修复后针对受影响业务日期和分区补跑,并完成数量、金额及核心指标校验后再恢复发布。
sql
银行数仓经验
+
DolphinScheduler
+
数据质量
+
补数
+
生产责任