# 大数据开发面试题库

0 阅读11分钟

大数据开发岗面试必备:SQL 高频题、Spark 性能调优、数仓建模实战、项目经验梳理,覆盖初中级到高级岗位


📌 前言

为什么面试总被问倒?
为什么项目经验说不清楚?
为什么调优问题总是泛泛而谈?

根本原因:没有系统准备面试题库!

本篇整理大数据开发岗高频面试题,涵盖:

  • 🔍 SQL 高频面试题(窗口函数、行转列)
  • 🔍 Spark 性能调优实战
  • 🔍 数仓建模设计题
  • 🔍 项目经验梳理方法
  • 🔍 行为面试题回答模板

题目均来自真实面试,附详细答案!


📊 SQL 高频面试题

题目 1:连续登录问题

题目描述:

用户登录表 user_login_log,字段:user_id, login_date

找出连续登录 3 天及以上的用户。

表结构:

CREATE TABLE user_login_log (
    user_id     STRING,
    login_date  STRING  -- 格式:yyyy-MM-dd
)
PARTITIONED BY (dt STRING);

示例数据:

user_idlogin_date
U0012026-03-01
U0012026-03-02
U0012026-03-03
U0012026-03-05
U0022026-03-01
U0022026-03-03
U0022026-03-05

预期结果:

user_idstart_dateend_datedays
U0012026-03-012026-03-033

解答:

WITH login_rank AS (
    SELECT
        user_id,
        login_date,
        -- 关键:日期减去行号,连续日期的差值相同
        DATE_SUB(login_date, ROW_NUMBER() OVER (
            PARTITION BY user_id ORDER BY login_date
        )) AS date_group
    FROM user_login_log
    WHERE dt >= '2026-03-01'
),

continuous_groups AS (
    SELECT
        user_id,
        date_group,
        MIN(login_date) AS start_date,
        MAX(login_date) AS end_date,
        COUNT(*) AS days
    FROM login_rank
    GROUP BY user_id, date_group
    HAVING COUNT(*) >= 3
)

SELECT * FROM continuous_groups;

考察点:

  • 窗口函数 ROW_NUMBER 使用
  • 连续问题的解题思路(日期 - 行号)
  • 分组聚合

题目 2:TopN 问题

题目描述:

订单表 order_info,字段:order_id, user_id, category_id, pay_amount, create_time

求每个品类下消费金额最高的 Top3 用户。

解答:

WITH user_category_amount AS (
    SELECT
        user_id,
        category_id,
        SUM(pay_amount) AS total_amount
    FROM order_info
    WHERE dt = '2026-03-24'
    GROUP BY user_id, category_id
),

ranked_users AS (
    SELECT
        user_id,
        category_id,
        total_amount,
        ROW_NUMBER() OVER (
            PARTITION BY category_id
            ORDER BY total_amount DESC
        ) AS rn
    FROM user_category_amount
)

SELECT
    user_id,
    category_id,
    total_amount,
    rn AS rank
FROM ranked_users
WHERE rn <= 3;

考察点:

  • 分组聚合
  • ROW_NUMBER 排名
  • TopN 问题通用解法

扩展:

  • 如果金额相同怎么处理? → 使用 DENSE_RANK
  • 如何取前 10%? → 使用 NTILE(10)

题目 3:行转列问题

题目描述:

学生成绩表 student_score,字段:student_id, subject, score

将科目转为列,输出:student_id, chinese, math, english

示例数据:

student_idsubjectscore
S001chinese85
S001math92
S001english78
S002chinese90
S002math88
S002english95

预期结果:

student_idchinesemathenglish
S001859278
S002908895

解答:

SELECT
    student_id,
    MAX(CASE WHEN subject = 'chinese' THEN score ELSE 0 END) AS chinese,
    MAX(CASE WHEN subject = 'math' THEN score ELSE 0 END) AS math,
    MAX(CASE WHEN subject = 'english' THEN score ELSE 0 END) AS english
FROM student_score
GROUP BY student_id;

考察点:

  • CASE WHEN 条件表达式
  • 行转列的核心思路(GROUP BY + 聚合)
  • MAX/SUM 的选择(单值用 MAX,多值用 SUM)

扩展:列转行

-- 使用 UNION ALL
SELECT student_id, 'chinese' AS subject, chinese AS score FROM score_wide
UNION ALL
SELECT student_id, 'math' AS subject, math AS score FROM score_wide
UNION ALL
SELECT student_id, 'english' AS subject, english AS score FROM score_wide;

-- 或使用 LATERAL VIEW explode (Hive 2.2+)

题目 4:留存率计算

题目描述:

用户登录表,计算次日留存率、7 日留存率。

留存率公式:

次日留存率 = 第 1 天登录且第 2 天也登录的用户数 / 第 1 天登录的用户数

解答:

WITH daily_users AS (
    SELECT
        user_id,
        TO_DATE(login_time) AS login_date
    FROM user_login_log
    WHERE dt >= '2026-03-01'
    GROUP BY user_id, TO_DATE(login_time)
),

retention AS (
    SELECT
        t1.login_date AS stat_date,
        COUNT(DISTINCT t1.user_id) AS day1_users,
        COUNT(DISTINCT CASE WHEN t2.user_id IS NOT NULL THEN t1.user_id END) AS day2_users,
        COUNT(DISTINCT CASE WHEN t3.user_id IS NOT NULL THEN t1.user_id END) AS day7_users
    FROM daily_users t1
    LEFT JOIN daily_users t2 
        ON t1.user_id = t2.user_id 
        AND t2.login_date = DATE_ADD(t1.login_date, 1)
    LEFT JOIN daily_users t3 
        ON t1.user_id = t3.user_id 
        AND t3.login_date = DATE_ADD(t1.login_date, 6)
    GROUP BY t1.login_date
)

SELECT
    stat_date,
    day1_users,
    day2_users,
    day7_users,
    ROUND(day2_users * 100.0 / day1_users, 2) AS retention_rate_1d,
    ROUND(day7_users * 100.0 / day1_users, 2) AS retention_rate_7d
FROM retention;

考察点:

  • 自连接查询
  • 留存率计算逻辑
  • 日期函数使用

题目 5:GMV 同比环比

题目描述:

订单表,计算每日 GMV 的同比(去年同天)、环比(昨天)、周环比(上周同天)。

解答:

WITH daily_gmv AS (
    SELECT
        TO_DATE(create_time) AS stat_date,
        SUM(pay_amount) AS gmv
    FROM order_info
    WHERE dt >= '2026-01-01'
    GROUP BY TO_DATE(create_time)
),

gmv_with_lag AS (
    SELECT
        stat_date,
        gmv,
        LAG(gmv, 1) OVER (ORDER BY stat_date) AS prev_day_gmv,
        LAG(gmv, 7) OVER (ORDER BY stat_date) AS prev_week_gmv,
        LAG(gmv, 365) OVER (ORDER BY stat_date) AS prev_year_gmv
    FROM daily_gmv
)

SELECT
    stat_date,
    gmv,
    ROUND((gmv - prev_day_gmv) * 100.0 / prev_day_gmv, 2) AS day_over_day,
    ROUND((gmv - prev_week_gmv) * 100.0 / prev_week_gmv, 2) AS week_over_week,
    ROUND((gmv - prev_year_gmv) * 100.0 / prev_year_gmv, 2) AS year_over_year
FROM gmv_with_lag;

考察点:

  • LAG 窗口函数
  • 同比环比计算
  • 时间序列分析

🔥 Spark 性能调优面试题

题目 1:数据倾斜如何处理?

面试官追问:

  • 怎么发现数据倾斜?
  • 有哪些解决方案?
  • 你实际用过哪种?

参考答案:

1. 现象识别:

- 大部分 Task 很快,少数 Task 特别慢
- Spark UI 查看 Stage 详情,Task 耗时差异大
- 查看 Shuffle Read/Write 数据量差异

2. 解决方案:

// 方案 1:Key 加盐(随机前缀)
val saltedRDD = originalRDD.map { case (key, value) =>
  val salt = scala.util.Random.nextInt(10)
  (s"${salt}_${key}", value)
}

// 局部聚合
val localAgg = saltedRDD.reduceByKey(_ + _)

// 去除盐前缀,全局聚合
val globalAgg = localAgg.map { case (key, value) =>
  (key.split("_")(1), value)
}.reduceByKey(_ + _)

// 方案 2:使用广播 Join(小表)
val broadcastSmallTable = spark.sparkContext.broadcast(smallTable.collect())
val result = largeTable.join(broadcastSmallTable.value)

// 方案 3:调整并行度
spark.conf.set("spark.sql.shuffle.partitions", "2000")

3. 实际案例:

场景:用户行为日志,某些热门用户数据量极大

解决:Key 加盐 + 两阶段聚合

效果:任务耗时从 2 小时降到 20 分钟

题目 2:Spark OOM 如何排查?

面试官追问:

  • Driver OOM 和 Executor OOM 有什么区别?
  • 如何定位内存泄漏?

参考答案:

1. OOM 类型:

Driver OOM:
- collect() 数据量过大
- 广播表太大
- 解决:增加 driver-memory,避免 collect

Executor OOM:
- 分区数据量过大
- 缓存过多
- 解决:增加 executor-memory,调整并行度

2. 排查步骤:

# 1. 查看 Spark UI 内存使用
Spark UI → Executors → Memory/Dis

# 2. 查看 GC 日志
--conf "spark.executor.extraJavaOptions=-XX:+PrintGCDetails"

# 3. 查看 Task 数据量
Spark UI → SQL → 查看 Shuffle Read/Write

# 4. 分析 Dump 文件(生产环境)
jmap -dump:format=b,file=heap.hprof <pid>

3. 解决方案:

// 调整内存配置
--conf spark.driver.memory=4g
--conf spark.executor.memory=8g
--conf spark.executor.memoryOverhead=2g

// 调整并行度
--conf spark.sql.shuffle.partitions=2000

// 避免 collect
// 错误:val data = df.collect()
// 正确:val data = df.take(1000)

// 及时清理缓存
df.unpersist()

题目 3:Shuffle 优化有哪些手段?

参考答案:

1. 减少 Shuffle:

// 使用广播 Join
val result = largeDF.join(broadcast(smallDF), "key")

// 预聚合
df.groupBy("key").agg(sum("value"))  // 会产生 Shuffle
df.reduceByKey(_ + _)  // Map 端预聚合

2. 调整 Shuffle 参数:

# 增加 Shuffle 分区数
spark.sql.shuffle.partitions=2000

# 调整 Shuffle 缓冲区
spark.shuffle.file.buffer=128k
spark.shuffle.spill.compress=true
spark.io.compression.codec=snappy

3. 使用高效 Shuffle:

# Spark 2.x+ 默认使用 SortShuffleManager
spark.shuffle.manager=sort

# 启用 Tungsten 优化
spark.sql.codegen.wholeStage=true

题目 4:Spark SQL 调优经验?

参考答案:

1. 启用 CBO(基于成本的优化):

spark.sql.cbo.enabled=true
spark.sql.cbo.joinReorder.enabled=true
spark.sql.statistics.histogram.enabled=true

2. 收集统计信息:

-- 表级别
ANALYZE TABLE order_info COMPUTE STATISTICS;

-- 列级别
ANALYZE TABLE order_info COMPUTE STATISTICS FOR COLUMNS user_id, pay_amount;

3. 谓词下推:

-- 错误:先 Join 再过滤
SELECT * FROM (
    SELECT t1.*, t2.user_name 
    FROM order_info t1 
    JOIN user_info t2 ON t1.user_id = t2.user_id
) WHERE dt = '2026-03-24';

-- 正确:先过滤再 Join(Spark 会自动优化)
SELECT t1.*, t2.user_name 
FROM order_info t1 
JOIN user_info t2 ON t1.user_id = t2.user_id
WHERE t1.dt = '2026-03-24';

4. 使用合适的数据格式:

Parquet > ORC > Avro > JSON/CSV
- Parquet 支持列式存储
- 支持谓词下推
- 压缩比高

🏗️ 数仓建模设计题

题目 1:设计一个电商数仓

面试官要求:

  • 画出分层架构
  • 说明每层作用
  • 给出关键表设计

参考答案:

1. 分层架构:

┌─────────────────────────────────────────────────────────────┐
│                      ADS 应用层                              │
│   GMV 大屏、用户留存报表、商品排行榜                          │
└─────────────────────────────────────────────────────────────┘
                           │
┌─────────────────────────────────────────────────────────────┐
│                      DWS 聚合层                              │
│   用户日汇总、商品日汇总、品类日汇总                          │
└─────────────────────────────────────────────────────────────┘
                           │
┌─────────────────────────────────────────────────────────────┐
│                      DWD 明细层                              │
│   订单事实表、用户维度表、商品维度表                          │
└─────────────────────────────────────────────────────────────┘
                           │
┌─────────────────────────────────────────────────────────────┐
│                      ODS 原始层                              │
│   业务数据库同步表(MySQL Binlog)                            │
└─────────────────────────────────────────────────────────────┘

2. 关键表设计:

-- 订单事实表(DWD)
CREATE TABLE dwd_fact_order (
    order_id          STRING      COMMENT '订单 ID',
    user_id           STRING      COMMENT '用户 ID',
    product_id        STRING      COMMENT '商品 ID',
    category_id       STRING      COMMENT '品类 ID',
    city_id           STRING      COMMENT '城市 ID',
    
    pay_amount        DECIMAL(18,2) COMMENT '支付金额',
    quantity          INT         COMMENT '数量',
    
    order_status      INT         COMMENT '订单状态',
    create_time       TIMESTAMP   COMMENT '下单时间',
    pay_time          TIMESTAMP   COMMENT '支付时间',
    
    dt                STRING      COMMENT '分区字段'
)
PARTITIONED BY (dt STRING)
STORED AS PARQUET;

-- 用户维度表(DWD,SCD2)
CREATE TABLE dwd_dim_user (
    user_key          BIGINT      COMMENT '代理键',
    user_id           STRING      COMMENT '用户 ID',
    user_name         STRING      COMMENT '用户名',
    gender            INT         COMMENT '性别',
    city_id           STRING      COMMENT '城市 ID',
    user_level        INT         COMMENT '用户等级',
    
    start_date        STRING      COMMENT '生效开始日期',
    end_date          STRING      COMMENT '生效结束日期',
    is_current        INT         COMMENT '是否当前版本'
)
STORED AS PARQUET;

-- 用户日汇总(DWS)
CREATE TABLE dws_user_daily_stat (
    user_id           STRING      COMMENT '用户 ID',
    stat_date         STRING      COMMENT '统计日期',
    
    gmv               DECIMAL(18,2) COMMENT 'GMV',
    order_count       INT         COMMENT '订单数',
    pay_amount        DECIMAL(18,2) COMMENT '支付金额',
    refund_count      INT         COMMENT '退款次数',
    
    dt                STRING      COMMENT '分区字段'
)
PARTITIONED BY (dt STRING)
STORED AS PARQUET;

题目 2:如何设计缓慢变化维?

参考答案:

1. SCD 类型选择:

类型处理方式适用场景
Type 1直接覆盖纠错、不重要属性
Type 2新增行(推荐)用户等级、地址等
Type 3新增列只保留上一版本

2. Type 2 实现:

-- 表结构增加时间范围
CREATE TABLE dwd_dim_user_scd2 (
    user_key          BIGINT,
    user_id           STRING,
    user_level        INT,
    start_date        STRING,
    end_date          STRING,
    is_current        INT
);

-- ETL 逻辑
-- 步骤 1:关闭旧记录
UPDATE dwd_dim_user_scd2
SET is_current = 0, end_date = '2026-03-23'
WHERE user_id IN (SELECT user_id FROM ods_user WHERE dt = '2026-03-24')
  AND is_current = 1;

-- 步骤 2:插入新记录
INSERT INTO dwd_dim_user_scd2
SELECT
    ROW_NUMBER() OVER () + max_key,
    user_id,
    user_level,
    '2026-03-24' AS start_date,
    '9999-12-31' AS end_date,
    1 AS is_current
FROM ods_user
CROSS JOIN (SELECT MAX(user_key) AS max_key FROM dwd_dim_user_scd2);

💼 项目经验梳理

如何介绍项目?

STAR 法则:

S(Situation):背景
  - 公司业务:电商平台,日订单 100 万
  - 数据痛点:数据分散、口径不一、查询慢

T(Task):任务
  - 搭建实时数仓
  - 统一数据口径
  - 提升查询性能

A(Action):行动
  - 技术选型:Flink + Kafka + Doris
  - 分层设计:ODS/DWD/DWS/ADS
  - 性能优化:并行度调整、状态管理

R(Result):结果
  - 数据延迟:从小时级降到秒级
  - 查询性能:提升 10 倍
  - 业务价值:支持实时运营决策

项目难点及解决

准备 3 个难点:

难点 1:数据倾斜
- 现象:热门用户数据量极大
- 解决:Key 加盐 + 两阶段聚合
- 效果:任务耗时从 2h 降到 20min

难点 2:数据一致性
- 现象:实时与离线数据不一致
- 解决:Exactly-Once + 去重逻辑
- 效果:准确率 99.9%+

难点 3:状态管理
- 现象:状态过大导致 OOM
- 解决:RocksDB + 状态 TTL
- 效果:稳定运行 30 天 +

🎯 行为面试题

"你的优缺点是什么?"

优点(结合岗位):

- 学习能力强:1 个月掌握 Flink 技术栈
- 责任心强:生产问题 30 分钟内响应
- 团队协作:主动分享技术文档

缺点(真实但可改进):

- 公开演讲紧张:正在通过内部分享锻炼
- 过于追求完美:学习时间管理,优先完成

"为什么离职?"

回答模板:

- 感谢前公司培养(感恩)
- 个人职业规划(发展)
- 寻求更大挑战(积极)

示例:
"感谢前公司给我成长的机会,但业务相对稳定,
我希望寻求更大挑战,接触更复杂的技术场景。"

"期望薪资多少?"

回答策略:

- 先了解岗位预算范围
- 给出区间而非具体数字
- 强调能力匹配

示例:
"我相信贵公司有成熟的薪酬体系,
根据我的经验和技术水平,期望在 X-Y 范围,
具体可以根据面试表现调整。"

📋 面试准备清单

技术准备

  • SQL 窗口函数熟练
  • Spark 调优原理
  • 数仓分层设计
  • 实时计算架构
  • 项目难点梳理

项目准备

  • STAR 法则整理
  • 3 个技术难点
  • 量化成果数据
  • 架构图画出来

行为准备

  • 优缺点回答
  • 离职原因
  • 职业规划
  • 反问问题

📚 推荐资源

刷题平台

  • LeetCode 数据库题(前 50 道)
  • 牛客网 SQL 专项练习
  • HackerRank SQL

技术博客

  • Apache Spark 官方博客
  • Flink 中文社区
  • 美团技术团队

书籍推荐

  • 《Spark 性能调优实战》
  • 《数据仓库工具箱》
  • 《流式架构原理与设计》

📌 总结

面试核心

1. SQL 是基础(窗口函数必考)
2. 调优是加分项(至少准备 3 个案例)
3. 项目要量化(用数据说话)
4. 表达要清晰(STAR 法则)

复习计划

时间内容
第 1 周SQL 刷题(每天 5 道)
第 2 周Spark/Flink 原理
第 3 周项目梳理 + 模拟面试
第 4 周行为面试 + 薪资谈判

💡 面试是双向选择,保持自信,展示真实的自己!