1.SQL:求连续登录天数
sql
login_log(
user_id,
login_date
)
同一用户一天可能登录多次。求每个用户最长连续登录天数。
核心考察点: 去重、窗口函数、row_number()、连续区间问题。
sql
WITH t1 AS (
SELECT DISTINCT
user_id,
login_date
FROM login_log
),
t2 AS (
SELECT
user_id,
login_date,
ROW_NUMBER() OVER (
PARTITION BY user_id
ORDER BY login_date
) AS rn
FROM t1
),
t3 AS (
SELECT
user_id,
login_date,
DATE_SUB(login_date, INTERVAL rn DAY) AS grp
FROM t2
),
t4 AS (
SELECT
user_id,
grp,
COUNT(*) AS continuous_days
FROM t3
GROUP BY user_id, grp
)
SELECT
user_id,
MAX(continuous_days) AS max_continuous_days
FROM t4
GROUP BY user_id;
我会先对 user_id 和 login_date 去重,因为同一天多次登录不能重复计算。然后按用户和日期排序生成 row_number。对于连续日期,日期减去序号后会得到相同的值,因此可以用这个值作为连续区间标识,再统计每段长度,最后取每个用户的最大值。
追问
为什么不能直接 lag()?
可以。lag() 可以判断当前日期和上一日期是不是相差一天,但之后还需要进一步生成分组编号。row_number + 日期偏移 对连续日期问题通常更简洁。
2.Hive 为什么不适合大量 UPDATE / DELETE?
Hive 本质上面向的是大规模离线分析,不是高频事务处理。
Hive 的数据通常以 Parquet、ORC 等文件形式存储在 HDFS 或对象存储中。
而大数据文件往往不是按照"单行记录随机修改"的方式组织的。修改少量记录可能意味着重新生成部分甚至整个数据文件,因此频繁 UPDATE/DELETE 成本很高。
Hive 虽然存在 ACID 表等机制支持事务操作,但它仍然不是为了高并发 OLTP 设计的。
因此典型数仓更常采用:
sql
增量写入
覆盖分区
Merge
拉链表
追问
那每天业务数据变化怎么办?
可以按照日期分区,只重算受影响的分区:
sql
dt=20260904
dt=20260905
Hive 的数据一般是怎么存储的?
Hive 本身主要提供表结构、元数据管理和 SQL 查询能力,实际数据通常以文件形式存储在 HDFS 或对象存储中。
文件格式常用 ORC 和 Parquet,它们都是列式存储格式。相比行式存储,列式存储在查询部分字段时可以减少不必要的数据读取,也更有利于压缩,因此比较适合 Hive 这种以分析查询为主的场景。
ORC 对 Hive 生态的支持比较成熟,Parquet 的跨引擎兼容性更好,在 Hive、Spark、Trino 等系统中都很常见。
至于底层存储,传统 Hadoop 架构一般使用 HDFS。HDFS 会把大文件切分成 Block,分散存储到多个节点,并通过副本机制提供容错能力。
现在云环境下也经常使用 S3、OSS 等对象存储代替 HDFS,这样可以实现计算和存储分离,计算资源可以独立扩缩容。
| 面试问题 | 核心回答 |
|---|---|
| Parquet / ORC 是什么? | 列式文件格式 |
| 为什么适合 Hive? | 列裁剪、压缩率高、减少 I/O,适合分析查询 |
| ORC 和 Parquet 区别? | ORC 与 Hive 生态结合成熟;Parquet 跨引擎生态更广 |
| HDFS 是什么? | 分布式文件系统,Block 分块 + 多节点存储 + 副本容错 |
| Hive 数据存在 Hive 里吗? | 严格来说实际数据通常存在 HDFS/对象存储的文件中,Hive 管理表结构和元数据并提供查询能力 |
| HDFS 和 Parquet 什么关系? | 不是同一层级:Parquet 是文件格式,HDFS 是文件存储的位置/系统 |
| 为什么现在用对象存储? | 存算分离、弹性扩缩容、降低集群存储维护成本 |
3.数仓为什么一定要分层?
数仓分层的核心不是为了增加表,而是为了降低不同处理阶段之间的耦合,提高数据复用性和可维护性。
sql
业务系统
↓
ODS
↓
DWD
↓
DWS
↓
ADS
ODS:
尽量保持源系统原貌。
解决:
原始数据是什么?
DWD:
完成清洗、标准化、维度退化等,形成较细粒度的事实数据。
解决:
发生了什么业务行为?
DWS:
围绕主题做公共聚合。
解决:
某个业务主题有哪些公共指标?
ADS:
直接服务报表、应用、接口。
重点是职责边界清晰。
4.Hive 出现数据倾斜,你怎么判断和处理?
如何判断?
查看任务执行情况:
大部分 Task 已完成,少数 Task 长时间运行;
不同 Reducer 输入数据量差异巨大;
某些 Key 出现频率异常高;
Shuffle Read 数据明显不均衡。
怎么处理?
需要根据倾斜来源处理。
热点 Key 聚合:加盐,两阶段聚合
Join 倾斜:
小表:
MapJoin / Broadcast Join
避免大规模 Shuffle。
NULL 倾斜:
大量 NULL 可能全部进入同一个分区。
如果 NULL 没有业务意义,可以过滤;或者给 NULL 增加随机值分散。
为什么加盐可以解决?
因为默认相同 Key 会通过 Hash 被发送到同一个 Reducer。
加盐改变 Key:
A → A_1 / A_2 / A_3
从而分散到多个 Reducer。
5. Spark 中什么时候会发生 Shuffle?
当数据需要跨分区重新分布时,一般会产生 Shuffle。
sql
groupByKey
reduceByKey
join
distinct
repartition
sortByKey
shuffle涉及:
sql
序列化
网络传输
磁盘 I/O
排序
反序列化
Spark 优化中一个非常重要的原则就是:
尽量减少不必要的 Shuffle。
reduceByKey 和 groupByKey 哪个更好?
通常 reduceByKey。
因为它可以在 Map 端先做局部聚合:
sql
Map-side Combine
减少 Shuffle 数据量。
6. Kafka 为什么这么快?
Kafka 高吞吐主要来自几个方面:
1.顺序写磁盘
Kafka 日志采用 append-only:
sql
message1
message2
message3
→ append
相比随机磁盘访问,顺序 I/O 性能非常高。
Page Cache
大量数据通过操作系统 Page Cache 处理,减少用户态应用自己维护缓存的成本。
批量处理
Producer 可以将多条消息 Batch 后发送:
sql
1 message × 1000 requests
变成:
sql
1000 messages × 1 batch
显著降低网络调用成本。
零拷贝
发送日志数据时可以减少:
Kernel → User → Kernel
之间不必要的数据复制。
Partition 并行
不同 Partition 可以并行读写。
追问
Partition 越多越好吗?
不是。
Partition 增加可以提高并行度,但也会增加:
文件句柄;
Broker 元数据;
Leader election 成本;
Consumer rebalance 成本。
7. Kafka Consumer Group 到底解决什么问题?
同一个 Consumer Group 内:
一个 Partition 同一时间只能被一个 Consumer 消费。
追问
4 Partition,10 Consumer 会怎样?、
只有最多 4 个 Consumer 真正工作。
Consumer 并行度 ≤ Partition 数量。
8. CDC 和每天定时抽数有什么区别?
考察点: CDC、Binlog、增量同步、实时数仓。
CDC:
sql
INSERT
UPDATE
DELETE
发生以后,从数据库日志中捕获变化。
例如 MySQL:
sql
Binlog
↓
CDC
↓
Kafka
CDC 优势
延迟低;
不需要不断扫描业务表;
能够捕获:
sql
INSERT
UPDATE
DELETE
的变化语义。
但 CDC 也更复杂
需要处理:
sql
重复事件
乱序
Schema Evolution
断点恢复
Exactly-once
追问
为什么不直接让业务系统把数据发 Kafka?
可以,而且很多系统就是这么设计。
但是 CDC 的优势是:
不要求侵入原有业务代码。
对于已经存在的大量传统业务系统尤其有价值。
9. Flink Checkpoint 到底保存了什么?
考察点: Stateful Streaming、容错。
Checkpoint 保存的是 Flink 作业在某个一致性时刻的状态快照。
恢复时:
Checkpoint State
对应数据源位置
共同恢复。
Kafka Source 中尤其重要的是:
消费到哪个 offset
追问
Checkpoint 和 Savepoint 区别?
Checkpoint:
系统自动进行,主要用于故障恢复。
Savepoint:
通常由用户主动触发,更偏向版本升级、迁移、维护。
10. Redis 已经有数据了,为什么还需要 Doris?
Redis:
适合:
sql
key → value
例如:
sql
user:1001:today_order_amount → 3280
特点:
极低延迟点查。
Doris:
适合:
sql
SELECT city,
category,
SUM(amount)
FROM orders
WHERE dt BETWEEN ...
GROUP BY city, category;
特点:
大规模 OLAP 聚合分析。
sql
Redis → 快速服务/缓存
Doris → 分析查询
面试口述一句话版本
Redis 更适合低延迟 Key-Value 查询,而 Doris 是面向分析型查询的 MPP OLAP 数据库,因此实时链路可以把部分热点指标写 Redis 服务在线查询,同时将明细或聚合数据写 Doris 支撑复杂分析。
11. 银行数仓场景:上游字段发生变化,你怎么办?
考察点: 数据治理、Schema Change、影响分析、生产意识。
假设省联社下发表原本:
sql
customer_id varchar(20)
突然改成:
sql
customer_id varchar(40)
甚至字段含义发生变化。
真正工作需要先做影响分析。
推荐流程
第一步:
确认变化:
sql
字段类型?
长度?
是否允许 NULL?
业务含义?
数据范围?
生效日期?
历史数据是否回刷?
第二步:
分析血缘。
sql
ODS
↓
DWD
↓
DWS
↓
ADS
↓
报表/接口
哪些表、存储过程、ETL Job、报表依赖这个字段。
第三步:
评估兼容性。
如果只是:
sql
varchar(20) → varchar(40)
通常风险较低。
如果:
sql
customer_id
含义从"客户号"变成"统一客户号",那就是业务语义变化,危险得多。
第四步:
修改并测试。
包括:
sql
开发环境验证
数据量核对
NULL检查
重复检查
上下游对账
第五步:
安排生产上线和回滚方案。
追问
如果已经导致当天报表错误怎么办?
先控制影响范围,暂停错误结果继续下发;定位受影响批次和表;修复逻辑后重跑对应链路,并对重跑前后数据进行对账;最后分析为什么字段变化没有提前被数据质量或 Schema 检查发现。
12. 场景题:昨天任务跑 20 分钟,今天突然跑了 3 小时,你怎么排查?
第一层:先判断变化来自哪里
对比昨天和今天
sql
数据量
SQL
执行计划
集群资源
上游数据
分区数量
Task 数量
第二层:看任务运行阶段
例如 Spark/Hive:
sql
Map 慢?
Shuffle 慢?
Reduce 慢?
少数 Task 慢?
所有 Task 都慢?
如果:
99% Task 完成,1 个 Reducer 卡两小时
高度怀疑:
数据倾斜
如果:
所有 Task 都比昨天慢
更可能:
sql
集群资源不足
I/O问题
网络问题
数据量整体暴涨
第三层:检查数据变化
sql
某分区异常
NULL暴增
热点Key
重复数据
上游补历史数据
第四层:检查 SQL / 执行计划
sql
新增 JOIN
JOIN 条件变化
过滤条件失效
分区裁剪失效
小表突然变成大表
第五层:根据原因处理
sql
热点 Key:
加盐 / 两阶段聚合。
小表 Join:
Broadcast / MapJoin。
数据量增长:
增加并行度、调整资源、优化分区。
SQL:
Predicate Pushdown、Partition Pruning、减少 Shuffle。
资源:
查看集群资源竞争并调整任务调度。
面试口述模板:
我不会一开始就假设是某一个问题,而会先和正常批次做对比。首先确认代码、数据量和集群资源有没有变化,然后通过执行日志判断耗时集中在哪个 Stage 和 Task。如果只有少量 Task 明显拖尾,我会重点排查数据倾斜;如果整体都变慢,则检查资源、I/O 和数据量。之后再检查上游数据分布、分区以及 SQL 执行计划,最后针对具体瓶颈做优化,并通过重跑和指标对比验证优化是否有效。