最近在整理数据仓库工程师的面试题,陆续收集了不少一线面试中反复出现的题目。说实话,这个行业的面试套路和刚入行时想象的不太一样——初级的题大多在考SQL和ETL基本功,中高级的题则会往建模思路、实时链路、数据治理这些方向深挖,不少题目看起来是开放题,实际是在考察你有没有真正在数仓里踩过坑。
这篇内容我会持续更新,每一道题都尽量把题目背后的考点讲透,而不只是给一个标准答案。因为数仓工程这个岗位的特殊性在于,答案本身往往不重要,更重要的是你推导答案的路径,以及你能不能解释清楚为什么这么设计。
1. 数仓面试到底在考什么
先聊聊我对数仓工程师面试的整体观察。搜集了大量实际案例之后会发现,面试官问的问题虽然千奇百怪,但总结下来就那么几个维度:建模能力、SQL功底、ETL经验、实时与离线架构理解、数据治理意识、以及沟通表达能力。
建模能力是最大的分水岭。很多面试者能熟练写出窗口函数,但一问到星型模型和雪花模型的区别就语焉不详;能说清楚拉链表的原理,但问什么时候该用拉链表、什么时候不该用,就开始含糊。建模是数仓工程师区别于普通数据分析师的核心技能,面试官一定会深挖。
SQL功底是入场券。几乎每一轮面试都会有一两道手写SQL题,风格从简单的分组TopN到复杂的连续登录、间隔问题都有。这些题看似是LeetCode风格,实际工作中真的会遇到,面试官想知道你写代码时是不是真的在考虑数据分布和执行效率。
ETL经验决定了你能不能落地。面试官会问你们怎么处理脏数据、调度失败怎么办、数据延迟怎么监控。这些问题的背后其实是在判断你有没有完整负责过一个数据链路的交付,而不是只写过几条查询语句。
实时和架构能力是加分项。现在数仓组的规模普遍不大,很多团队没有单独的实时团队,数仓工程师需要同时具备离线数仓和实时数仓的交付能力。面试官会问Lambda架构和Kappa架构的区别、Flink checkpoint机制对实时链路的影响,以及实时和离线数据对不上时怎么排查。
数据治理意识是隐藏考点。数仓做得越久,越会发现真正的难点不在开发,而在治理。元数据管理、数据血缘、指标一致性、数据质量监控,这些问题会以场景题的形式出现,考察的是你有没有全局视角。
所以,这篇面试题集我按模块来组织。需要注意的是,这些问题不存在标准答案,我的解析也只是给了一个参考思路,你可以结合自己在实际项目中的经验来做更贴合个人的回答。
2. 数仓建模高频题
2.1 星型、雪花、星座模型怎么选
这是数仓面试必问题,几乎没有例外。题目一般这样问:请介绍星型模型、雪花模型、星座模型的区别,你们实际用哪种,为什么。
星型模型是维度直接连接到事实表,维度表不会继续拆分;雪花模型是维度表进一步规范化,拆分出二级维度表;星座模型则是多个事实表共享同一套维度表。
回答时的关键不只是背定义,而是要讲清楚选型的逻辑。大多数互联网公司数仓以星型模型为主,因为查询路径短、理解成本低、join次数少。雪花模型在传统企业数仓里更常见,因为企业级数据模型通常要求规范化程度高、数据冗余小,但带来的代价是查询时多表关联,性能和易用性都有损耗。
实际项目中我见过一个比较典型的做法:底层采用雪花模型来统一口径,DWD层把维度字段冗余进去之后,对外呈现的是标准的星型结构。这其实是Inmon和Kimball两种方法论融合的实践,面试时可以主动提这一点,会体现你的架构视野。
星座模型在实际场景中非常普遍。一个电商数仓里,会员主题、交易主题、商品主题可能有多个事实表,都引用同一个商品维度表,这就是星座模型。面试官想听到的答案是:建模不是教科书上的死套路,而是以查询效率和易维护性为第一优先级。
还有个高频追问:什么是退化维度。这个要能解释清楚——维度表中只有唯一主键、没有其他属性时,可以直接退化到事实表中作为普通字段,减少一次join。比如订单ID、发票ID这类,很少会被单独当作维度表挂在事实表旁边,这背后本质是查询性能优化的思路。
2.2 缓慢变化维度的三种策略
缓慢变化维度的题目出现频率非常高,面试官通常会给一个具体场景:比如商品价格经常调整,用户地址偶尔变化,这种维度的历史信息需不需要保存,怎么保存。
SCD1是直接覆盖,不保留历史,适用于属性错误修正、或者对历史不敏感的字段。SCD2是保留历史,新增一条记录,通过生效日期、失效日期或者版本号来标识当前和历史数据,适用于需要精确回溯历史状态的场景。SCD3是增加上一版本字段,只保留最近一次变化记录,适用于只需要对比当前和上一次状态的场景。
回答这道题最忌讳的是背概念不举例。我建议用商品价格来举例:如果你想知道某个月每笔订单对应的商品当时的价格,就必须用SCD2,因为价格是不断变化的,覆盖掉就彻底丢了。但如果是商品类目名称调整,往往业务上只需要当前的值,SCD1就够了。
实现SCD2的时候有几个坑要提前讲出来。一是生效日期和失效日期的闭开区间问题,通常使用左闭右开,也就是生效日期当天生效、失效日期当天作废,这样保证连续区间没有缝隙也没有重叠。二是需要处理新插入和变更同时发生的情况,要保证原子性,一般在Hive里我们会把一个变更周期的操作包在一个事务里处理,或者使用目标表的merge语法做整表更新。
面试中如果笔试要求写SCD2的SQL,可以考虑给出一个比较简洁的实现思路:先查出当前的最新记录,将要更新的记录标记为失效,同时插入新记录。最稳妥的方式是分两步操作,先更新再插入,不要尝试一步到位。这个回答能体现出你实际做过,而不是只背过理论。
2.3 事实表的三种类型怎么选
事实表分类也是建模模块的高频题。事务事实表、周期快照事实表、累积快照事实表,这三者的区别和应用场景需要非常清晰地讲出来。
事务事实表记录的是业务事件发生的每个瞬间,比如订单表每下一个单就产生一条记录,特点是增量追加、事实不可变。周期快照事实表记录的是某个周期性时点的状态,比如库存表每天记录一次所有SKU的库存量,特点是定期覆盖或者定期追加,关注的是状态的快照。累积快照事实表记录的是业务过程从开始到结束的各个里程碑节点时间,比如订单从下单、支付、发货到签收,把所有关键时间点都放在一行里。
选型的逻辑要结合业务场景来讲。如果需要分析每天新增的订单量、销售额,事务事实表就够了;如果需要分析库存水平的变化趋势,就得用周期快照;如果需要分析一个订单从下单到签收花费了多少时间、哪个环节耗时最长,就需要累积快照,因为你要在一条记录里看到多个时间字段。
我实际项目中用得最多的反而不是事务事实表,恰恰是周期快照。因为很多业务分析关心的是存量状态,比如每日用户余额、每日库存水平、每日会员等级分布,这些都需要周期性地对整个表做一次全量快照。面试时可以举自己项目里的例子,比教科书案例更有说服力。
还有一个延伸考点:累计快照事实表在实时数仓里怎么实现。这里可以提到实时链路中我们是把同一个业务过程的多个事件拼接在一起,通过主键更新来维护一条生命周期记录,底层用Flink的upsert或者HBase的rowkey机制。这个补充信息对面试官来说往往是一个加分信号。
2.4 一套完整建模场景题
建模题还有一种出法,直接给一个业务场景让你现场设计模型。比如下面这个经典题目:请为一个电商平台设计一个会员订单分析模型,需要支持分析会员的消费频次、复购率、客单价、品类偏好等指标。
做题时重点输出思路,而不是一上来就写表结构。我的回答路径一般是这样:
先明确业务过程,这里核心业务过程是下单。确认粒度,每一行记录应该是一个订单,或者一个订单明细(如果同一订单包含多个商品),粒度定义直接决定后续所有指标的可追溯性。然后明确维度,会员、商品、品类、店铺、时间、渠道,这些是分析的角度。最后明确事实,订单金额、商品数量、优惠金额、运费,以及下单、支付、发货等关键时间点。
基于这个思路,可以设计一张交易事实表,粒度是订单明细级别,包含若干维度外键和度量字段;另外单独建会员维度表、商品维度表、日期维度表。如果还想支持复购率分析,需要基于这张事实表按会员维度做汇总,形成一个会员分析汇总表。
面试官通常会在你给出设计后追问一个问题:这个模型的粒度能不能支撑每个会员最近一次购买时间的分析。如果能,你的粒度设计就是合理的;如果不能,说明需要调整设计。这个追问非常经典,因为很多面试者在设计表时只想着覆盖当前指标,没有去核对粒度和可追溯性。
我在回答这类题时会主动画一条思路链路:指标拆解到粒度,粒度决定事实表,维度挂在事实表旁边,汇总表解决性能问题。面试官听到这个逻辑,基本就能判断出你是有建模思维的人,而不是只会建表的人。
3. ETL与数据加工经典题
3.1 数据去重方案怎么答
去重题看似简单,实际有很多细节。面试中最常见的问法:有一张用户行为日志表,每个用户每天可能产生多条记录,现在需要每个用户每天只保留一条记录,你怎么做。
最自然的答案是使用row_number()加Rank=1的方式,按用户和时间分区,对某个自增字段或者时间倒序排序,取第一条。如果再问一句能不能用distinct,你要能区分两个场景:如果整行数据完全相同,distinct可以解决问题;但如果你需要“保留每个用户最新的那条”,distinct做不到,因为你要的是按某个字段排序后去重,而不是按整行去重。
还有一种场景是业务主键去重。比如同步数据时上游给了一份全量数据,里面可能存在重复的订单ID,需要保证每个订单ID只保留一条。这种情况可以用窗口函数,也可以使用聚合取max(update_time)的方式。如果数据量特别大,join的时候要注意数据膨胀问题。
我实际踩过的一个坑是:在Hive里使用row_number时没有控制分区内数据量,有些用户一天产生几万条日志,造出了几百个reduce数据倾斜。后来采用的方案是加盐分组,或者把每个分区拆成更细粒度后再合并。面试中主动提到这个问题会是一个很好的加分点,因为大多数面试者不会涉及数据倾斜。
手动写一个去重SQL需要注意execution顺序。先去重再聚合,跟先聚合再去重性能差异很大。一般原则是:能在源头过滤掉的不要等到下游再过滤。比如内层先过滤掉明显无效的数据,再去做去重排序,可以减少shuffle的数据量。
3.2 拉链表设计原理
拉链表几乎是数仓面试必考的数据结构题。常见问法:你们用户表是每天全量同步还是增量同步?如果用户信息每天都会发生变化,你又想查历史某一天的快照,怎么做。
拉链表的核心思路是每条记录增加start_date和end_date两个时间字段,记录这个用户某个状态的生效周期。查询任意一天的快照都变成了一个简单的过滤条件:某天落在start_date和end_date之间即为当天的数据。
回答时一定要明确四个场景。初始化场景,一般把全量历史数据导入,start_date设为历史最早日期,end_date设为远期的最大日期。增量更新场景,根据上游变更日志,每天把变化的用户找出来,将旧记录end_date更新为前一天,同时插入新记录。查询场景,查某个时间点的快照,一个where条件搞定。删除场景,用户的物理删除在拉链表里一般只做标记不做物理删除,保证历史可回溯。
面试官经常会追问一个性能问题:拉链表越来越大,查询变慢怎么办。有几个思路可以讲,一是按start_date做分区,将每日新增的数据放到独立分区里;二是查询时如果只需要当前最新数据,可以单独拿出一张当前有效表;三是使用索引或者对活跃用户和非活跃用户做拆分。这几种优化方式在Hive和Spark环境下的效果不太一样,可以展开讲讲。
拉链表的替代方案也值得提一下。比如Hive的ACID表或者Iceberg可以支持时间旅行查询,天然就能查历史快照,不需要手动维护拉链逻辑。但要么有性能开销,要么需要团队具备一定的新引擎能力。在实际选择时,不是每个团队都有条件上新的技术栈,拉链表仍然是很稳定可靠的经典方案。
3.3 窗口函数必考题型
窗口函数是SQL手写题的常客,考点主要集中在排名类、累计类、偏移类三大类。每一次面试至少会覆盖其中一到两类。
排名类最典型的是分组TopN。比如求每个品类销售额前3的商品,用row_number、rank、dense_rank三者的区别要讲清楚,row_number是唯一连续序号,rank会跳号,dense_rank不跳号。实际业务中一般优先使用row_number,因为它最适合“取前N条”这种场景。
累计类最经典的是求连续登录天数。这里有一个非常巧妙的解法,使用date_sub(login_date, row_number() over (partition by user_id order by login_date)),把连续日期的差值变成一个恒定值,再按这个差值分组统计。这个解法很经典但很多人讲不清楚原理。原因其实很简单:如果用户连续登录,日期每次加1,序号也每次加1,差值就不变;一旦中断,序号不连续,差值就会变化。面试时把这个原理讲透比背写法更有用。
偏移类最常见的是同比环比。去年同期、上月同期、环比增长率,这些都需要使用lag和lead函数来取前后行的数据。同比环比计算要注意一个坑,不要直接拿当期值和上期值相除完事,要处理分母为0的问题,以及用case when来对空值做处理。
窗口函数题还会大量出现在“留存率”“访问时长”这些分析场景中。比如求每日新增用户在之后第7天的留存率,虽然也可以用join来实现,但窗口函数配合date_diff可以做得很优雅。面试时如果你的回答能同时提供两种写法,并说明各自适合的数据规模,会给面试官留下很好的印象。
3.4 漏斗分析类问题
漏斗分析题常见的出法:给一个用户行为事件表,包含用户ID、时间、事件类型(如曝光、点击、加购、下单、支付),求整体转化率和每一步之间的转化率。
这道题的考点一方面是SQL能力,另一方面是业务理解能力。答案是先每个事件独立统计人数,再算相邻阶段的人数比值。关键是要明确应该按用户维度去重,因为一个用户可能在不同阶段产生多次行为,但漏斗分析关注的是人数而不是次数。
实现时常见的写法有两种:一种是使用join,每个阶段算一遍人数,再left join在一起;另一种是使用条件聚合加case when判断用户是否完成了后续步骤。在大数据量场景下,第二种性能更好,因为它只需要扫描一次表。回答时可以主动比较两种方案的效率差异。
面试官追问时通常会给一个细节场景:如果用户跳过了加购直接下单,算不算漏斗流失。这个问题没有标准答案,关键是你要体现出对口径的思考。一般来说,漏斗步骤可以根据业务需求灵活定义,但所有分析都要保证口径一致,并且在产出报表时明确说明“本漏斗的定义是每步完整按顺序进行”。
我还会补充一个实战经验:漏斗分析在实时数仓中也常用,比如实时监控异常转化率。把一个漏斗的每个步骤做成实时计数,再通过一个轻量级规则引擎去做阈值告警,这是很典型的实时数仓业务场景。面试时能把这层经验聊出来,说明你不只是停留在写SQL的层面。
4. 离线和实时数仓架构
4.1 离线数仓分层架构
分层问题几乎必考,问法一般是:你们的数仓怎么分层,每层做了什么,为什么要这么分。
标准回答是四层:ODS、DWD、DWS、ADS。ODS层存储原始数据,保持和源系统一致,不做任何加工。DWD层做清洗、去重、维度退化、格式规范化,形成明细宽表。DWS层做公共汇总,按主题组织,比如会员主题汇总表、商品主题汇总表。ADS层面向具体应用,按业务方的报表需求定制数据。
在讲清楚是什么之后,要加强调分层的价值:成本管控、复用性、隔离性。ODS层保证原始数据永久可回溯,DWD层保障口径统一,DWS层推动指标的复用,ADS层隔离了业务变化对底层的影响。面试官喜欢听到“复用”这两个字,因为很多团队分层做得很差,每次开发新报表都要从ODS开始算,人力浪费极大。
有一个分层误区值得在面试中主动提:很多人会把DWD层做成完全的明细拉宽表,导致所有业务分析都要走DWD,性能很差。我的建议是,DWD层要适度做维度冗余和清洗,但不要试图解决所有分析问题。通用的轻度汇总放到DWS层,定制化的计算放到ADS层,职责边界清晰了,整个数仓的效率才会高。
如果面试官再追问一个问题:ODS层和源系统重复存储会不会很浪费。可以这样回答:ODS层存储的是不可变的原始数据,它最大的价值在于提供回溯能力。尤其是在源系统表结构变更、数据被误删除、业务口径需要重新计算时,ODS层是最后的保险。成本上可以通过压缩、分区保留策略、冷热分层来优化。
4.2 Lambda架构和Kappa架构对比
实时数仓部分问得最多的是架构模式题,尤其是Lambda和Kappa的区别。这道题既考你的理论积累,又考你有没有真实的实时项目经验。
Lambda架构的完整答案是:离线链路和实时链路并存,同一份数据分别经过批处理和流处理,在服务层合并。优点是可以同时兼顾吞吐量和低延迟;缺点是同一套逻辑要维护两套代码,开发成本高,还可能存在数据对不上的问题。
Kappa架构的思路是用流处理统一处理一切,数据以日志的形式保留在消息队列中,通过完整的流处理链路计算所有结果,不单独建设离线链路。优点是同一套代码,逻辑统一,运维成本低;缺点是流处理框架对历史数据重算能力要求比较高,且对消息队列的数据保留时长和吞吐量有要求。
面试官在问这道题时真正想听的是你的选型判断。实际项目中不可能盲目选一个标准架构,而是要基于团队情况和业务需求来判断——数据量级是多少、延迟要求是分钟级还是秒级、团队里有没有能维护Flink的人、历史数据是否需要重复计算、离线和实时结果是否必须保证一致。把这些问题挨个过一遍,你的回答就会比背出来的概念有说服力得多。
我的实际感受是,大多数互联网团队最终都走了Lambda架构。Kappa很美好,但真正的痛点是:纯流处理做复杂的数据重跑、回溯和口径调整非常痛苦。很多团队只是把实时链路作为离线链路的补充,核心报表仍然以离线为准。面试时可以据实说你们选了哪种方案,重点是要把你的决策理由讲清楚。
4.3 实时数仓的分层
实时数仓分层是被问到的高频题,因为大部分数仓工程师对实时链路不熟。如果你有相关经验,这道题会拉开很大的差距。
实时分层在思路和离线分层是对应的,通常也是四层。实时ODS层在消息队列中存储原始日志,比如Kafka中的业务日志和binlog。实时DWD层做清洗和关联,通常会从维表读取维度信息,把明细数据写成Kafka的明细主题。实时DWS层做窗口聚合,按分钟或小时粒度统计汇总,结果写到OLAP引擎。实时ADS层直接面向应用,通常是高基数的明细查询或者看板数据。
面试官会追问:实时DWD层维表关联是怎么实现的。要知道几种主流的方案:Flink join维表可以通过异步IO查Redis、或通过广播维表到本地Cache、或通过HBase存储维表快照。这里考察的是你对Flink常用技巧的熟悉度,能详细讲出一种方案就基本过关。
另一个高频追问:实时和离线数据不一致怎么办。我给出的建议是:以离线为准做数据对账,定期比对离线K8s和实时结果,发现偏差时以离线数据修正实时结果。实时链路的定位延迟要低,但准确性不必做到和离线完全一致,因为实时看板更多用来快速发现趋势变化,而不是作为财务口径依据。
4.4 实时计算中数据一致性问题
一致性问题是实时链路里杀伤力最大的。面试官经常会问:实时任务重启后数据会不会重复、会不会丢,怎么保证端到端一致性。
首先需要解释清楚三个级别:At-most-once、At-least-once、Exactly-once。大多数实时链路默认是At-least-once,也就是任务重启后会发生重复消费。要保证数据不重复不丢失,在Flink里需要开启checkpoint并设置exactly-once语义,下游要支持幂等写入。
接下来是实际经验。在真实的实时链路中,很多问题不是出在Flink内部,而是出在下游存储。比如Kafka producer重试可能导致重复写入,下游存储如果没有做幂等去重,最终的数据就会多出来。所以我们的实时链路会在写入下游的时候按照业务主键做一次去重,或者使用支持upsert语义的存储引擎。
面试官还会问到一个高频问题:上游数据的先后顺序乱了怎么办,比如一个用户先产生了支付事件,后产生了加购事件,按到达顺序去重时会把正确逻辑打乱。这需要使用事件时间而不是处理时间来做计算,同时配合watermark和allowedLateness处理乱序数据。回答时能够举一个乱序数据的实际处理案例,会非常加分。
5. 数据治理题怎么答
5.1 数据质量监控怎么做
数据质量治理是数仓面试中越来越常出现的考点。面试官知道现在每个团队都在建数据质量体系,问这类题可以看出你在实际问题面前有没有全局意识。
回答时从五个维度展开:完整性、准确性、唯一性、一致性、及时性。完整性看字段的空值率;准确性看数据内容、格式、枚举值是否符合预期;唯一性看主键是否重复;一致性看同名指标在不同表、不同口径下是否一致;及时性看数据产出时间是否满足业务时效要求。
还要讲清楚监控的执行方式。一般是三部分:离线定时任务跑质量校验SQL、实时规则引擎处理流式数据校验、元数据中心负责规则配置和告警订阅。质量校验SQL通常是先统计异常指标,再和阈值做比较,异常时触发告警。
面试时可以举一个实际案例:曾经有一个日活报表,某天突然比昨天涨了30%,排查发现是上游埋点SDK发布了新版本,多上报了一种新事件类型,导致DWD层清洗逻辑没有过滤掉新事件,直接算进了日活。后来我们在DWD层增加了枚举白名单校验,规则包括事件类型只能是指定集合内的值,一旦出现未知枚举值立刻告警。这类案例很能说明你具备数据质量的实战经验。
面试官会追问质量规则怎么配置才能减少误报漏报。这里可以讲述动态阈值和固定阈值结合的方式:固定阈值适合硬性约束,比如主键重复必须为0;动态阈值适合指标波动类约束,也就是按历史数据分布自动计算合理波动范围。这两种规则配合使用,既能防止大事故,也能避免天天被无效告警骚扰。
5.2 数据血缘和元数据管理
数据血缘在数仓面试里被问到的频率越来越高了。因为几乎所有团队都在做数据治理平台,血缘是数据治理的核心能力。
需要分几个层面来讲。血缘分字段级血缘和表级血缘,也可以区分为解析SQL得到的静态血缘,和通过运行时产生的实际数据传递关系得到的动态血缘。静态血缘适合做影响分析和链路展示,动态血缘适合做成本分析和质量归因。
表级血缘的构建方式通常是从调度系统中的作业依赖里提取:每个作业的输入表和输出表明确了,表之间的依赖关系也就出来了。字段级血缘则麻烦得多,一般通过解析SQL的AST加运行时信息来获取。面试时能区分这两种血缘,面试官就知道你确实接触过血缘系统。
元数据管理面试考点集中在数据字典、指标口径和热度管理。数据字典是给业务同学用的,关键字是便于检索。指标口径管理的关键在于“唯一来源”,每个指标只能定义在一个地方,其他报表引用时通过指标ID关联,避免同一指标在不同报表存在不同计算逻辑。这个踩坑经历非常普遍,我建议面试时把这部分讲成案例,比抽象讲概念更有说服力。
血缘还有一个实际用途要提到:做故障影响排查。某张ODS表Kafka同步挂了,依赖这张表的几十张下游表都会受影响,血缘关系可以帮你快速评估影响范围。实际场景中很多团队是从故障复盘后才开始建设血缘系统的,这个背景故事讲出来会拉高回答的真实性。
5.3 数据权限与安全合规
数据安全类题目在面试中出现时,常见问法:一个销售团队想看用户画像数据,但你判断这个请求存在越权嫌疑,怎么处理。
回答思路是提出数据权限分层的方案:原始数据层严格管控,脱敏数据层供研发测试使用,汇总数据层供业务分析使用。不同角色匹配不同权限,权限申请必须走审批流程,且审批日志需要留存可追溯。
敏感数据的处理也有常见方案:身份证、手机号等字段在下发前强制脱敏,可以采用保留前3后4的掩码方式,或者更安全的令牌化方式。这里要主动讲字段加解密、密钥管理、敏感字段识别这几个环节,体现你有安全工程意识而不是只会写SQL。
面试官还会追问一个细节:数据仓库的权限管理具体怎么落地。可以回答几个主流方案:Hive的Sentry/Ranger做表级和行级权限控制,数仓开发时尽量走统一的查询入口,通过网关层做鉴权和数据脱敏。再加上一个重要原则:权限最小化,也就是每个角色默认只能看到自己业务需要的那部分数据,而不是授权一个库的读权限。
在谈权限的同时要留意合规背景,避免提及任何具体的法规名称。可以谈通用的安全理念,比如“敏感数据必须做到分类分级”、“外部数据共享前必须脱敏”、“权限申请要可追溯可审计”。这些表述安全且专业,面试官听完也能感受到你的合规意识。
6. SQL与调优实战
6.1 连续问题SQL怎么写
连续问题就是给定一张日期表,求连续出现的次数。这是一道非常经典的SQL题,必须完整掌握。
给出完整思路:如果一张表有用户ID和登录日期,现在要找连续登录超过3天的用户,先对每个用户按日期去重,然后用row_number按日期排序。关键步骤是date_sub(login_date, rn),这个差值如果是同一个值,说明日期是连续的;再按用户和差值分组,统计组内记录数,大于等于3的就是连续登录3天以上的用户。
写出一个可运行示例:
with t1 as ( select user_id, login_date, row_number() over (partition by user_id order by login_date) as rn from user_login_daily group by user_id, login_date ), t2 as ( select user_id, date_sub(login_date, rn) as diff from t1 ) select user_id from t2 group by user_id, diff having count(*) >= 3;面试过程中建议不只是写出SQL,还要解释这个思路为什么成立。可以拿日历打比方:连续7天登录,日期与序号的差相同,一旦漏了一天,这个差值就会变。括号里加了一句“注意字段类型和分区裁剪”,能体现你在意执行性能。
如果日期列是DATETIME包含时分秒,需要先CAST到DATE或者用to_date处理,否则去重和date_sub会得到错误结果。这个问题我在真实项目中遇到过多次,是一个值得提前铺开的细节。
6.2 数据倾斜的定位与处理
数据倾斜是数仓面试必考的高频题。最常见的问法是:你的Hive任务一直卡在99%,怎么排查和优化。
没有思路的人会直接说加资源、调参数,这恰恰是面试官不想听到的。一个有经验的回答路径应该是:先确认现象,看看是不是某个reduce卡住很久;然后定位原因,可能是关联键存在热点值,可能是group by时某个分组过大,也可能是空值太多导致倾斜。
举一个最常见的场景:两张表关联,一张是用户维表,另一张是订单事实表,join上去之后发现某个key(比如一个用户)关联出的数据量特别大,导致单个reduce处理时间极长。解决思路是拆分热点key:把热key单独拿出来,加随机前缀打散后再join;非热点key走正常join,最后union结果。这里的原理是让数据不再集中于单个reduce。
如果面试官问group by导致的倾斜,一般可以改用两阶段聚合:先加随机前缀打散分组,完成部分聚合,再去掉前缀做最终的聚合。这种写法能有效控制单key数据量。在Spark里可以通过repartition或salting来调整分区策略,在Hive里则要善用hive.groupby.skewindata参数,但需要注意这个参数会触发额外的shuffle,对非倾斜场景反而造成性能下降。
面试中如果能主动讲出你对“倾斜是数据分布问题,不是并发资源问题”的理解,面试官就会觉得你是真的排查过这类问题,而不是背书。
6.3 小文件问题怎么根治
小文件问题几乎在每一个大数据团队里都存在,面试官问它本质上是考察你有没有处理过大集群规模问题的经验。
小文件产生的主要来源:流式写入、动态分区写入了过多分区、Spark的并行度过高、不合理地使用insert overwrite只更新少量数据。小文件多会导致NameNode压力大、Spark/Hive读取性能差、任务启动时间长。
治本的方法要从写入端管起。在Flink写入Hive时要有意控制并行度和文件滚动策略,Hive中通过调整分区和文件合并参数比如合并小文件参数来减少文件数。原理上可以这样解释:文件数是写入并行度与分区数共同决定的,随意调大并发会导致文件数暴增,所以控制并发在上游就要做好。
治标的方法是在数仓例行任务中加入合并任务,比如每天收数之后做一次简单文件合并,把大量小文件重写为少量大文件。我建议合并任务放到DWD层写入时统一处理,不要在ODS层频繁合并,因为ODS层的数据是需要保留原始状态的。
回答时可以加一个体感说明:一个集群如果小文件数量持续增长,迟早会拖垮整个数据处理链路。我见过某个任务的日志量从几百GB涨到1TB,但文件数增长了一百倍,最后任务的调度时间全花在文件开关上了。这种描述会让面试官觉得你是真在大集群环境里遇到过问题的人。
6.4 手写复杂业务SQL
复杂业务SQL题经常是一道压轴题,尤其面试官要求你现场写代码时,务必保持结构清晰,分步拆SQL而不是一口气写很长的嵌套。
常见的复杂SQL场景有:跨多个粒度计算指标、同环比计算、累计值计算、留存率计算、会话级分析。
举一个留存率计算的例子:给定用户登录日志,计算某日新增用户在次日、7日、30日的留存人数。思路是找到新增用户的首次登录日期,然后关联这些用户在其他日期的登录行为,计算每个日期的距今时间差。关键在于首次登录日期和登录日期的时间差计算必须用datediff,口径统一。
with first_login as ( select user_id, min(login_date) as first_date from user_login_daily group by user_id ), login_dated as ( select user_id, login_date, datediff(login_date, first_date) as datediff_val from user_login_daily join first_login using (user_id) ) select first_date, count(distinct case when datediff_val = 1 then user_id end) as day1_retained, count(distinct case when datediff_val = 7 then user_id end) as day7_retained, count(distinct case when datediff_val = 30 then user_id end) as day30_retained from login_dated group by first_date;这条SQL的考点有三个:用min函数识别新增用户、用datediff做精确的时间差计算、用case when和count(distinct)做条件计数。手写时要注意字段命名清晰,现场不要想着优化到极致,写出可读性强的代码更重要。
面试官还会问性能优化空间,可以回答内层join时先用过滤条件缩小数据集、计算留存时只保留key日期而不是全时间范围、缓存用户表进行广播关联等。层层递进的回答会显得你对执行计划有很好的把控感。
7. 综合实战场景题
7.1 场景题的回答框架
很多面试官在最后一轮都会出一到两个场景题,比如:日活今天突然下降20%、报表数据比昨天多了10倍、某个核心指标和运营手工统计的对不上。这种题没有标准答案,但有最优回答路径。
我的建议是套用一个四步框架:明确问题、拆解可能原因、逐个排除验证、给出解决方案和预防措施。这个框架看起来简单,但绝大多数面试者做不到完整,他们往往直接跳到“可能原因”然后列了一堆,缺乏逻辑层次。
举例来说,假设线上数据任务某天凌晨全部延迟,首先明确现象,是全部都延迟还是只有部分,是从ODS层延迟还是只是某个报表延迟。然后拆可能原因:上游同步延迟、队列资源被抢占、任务依赖断裂、数据量激增、SQL执行计划劣化、集群故障。接着说明怎么定位:查看调度日志确定卡住位置、检查上游数据到达时间、看集群资源监控判断是否资源紧张、跑几个explain看执行计划变化。最后讲对策和预防:延迟容错机制、关键任务超时告警、重跑机制、元数据监控。
场景题的回答过程中,主动强调“先看数据,再谈业务”往往能让面试官眼前一亮。很多人一到场景题就开始猜业务原因,而数据工程师的第一反应应该是核对上游数据和自己计算逻辑有没有问题,数据先自洽,再去评估业务波动。
7.2 日活下降排查的完整示例
以最经典的日活下降场景为例,完整走一遍回答过程,让你感受面试时应该怎么组织语言。
先说排查顺序:先看数据质量,再看口径变化,然后看业务因素。数据质量层面检查当天ODS同步是否完整、有没有分区数据缺失或者重复,ETL清洗逻辑是否有变更导致大量数据被过滤;口径层面检查有没有新上线代码修改了日活的定义,有没有新增过滤条件,有没有上游埋点调整导致事件类型变化;排除了以上两层后,才考虑业务因素,大学还是工作日、有没有竞品活动、有没有特定渠道投放减少。
候选人的回答如果能提到“先确认是上游源头的问题还是本身逻辑的问题”,就说明他有成熟的排查思路。这时还可以举一个真实的例子:某次日活下降被定位到是因为埋点SDK版本升级,新SDK对某些旧事件类型不再上报,而DWD层没有及时加白名单,导致大量用户在清洗环节被过滤掉。这个案例最能体现数据质量和口径变更的联动作用。
面试官很可能追问一句:如果确认是业务因素而不是数据问题,你接下来怎么做。我会说:输出一个完整的影响评估报告,包括下降幅度、影响渠道、影响用户群体画像、和类似历史波动对比;后续建议业务方做针对性的渠道复盘和活动调整;同时把排查过程沉淀成自动化监控规则,下次同类波动直接生成诊断结论。这样收尾,整个回答的格局就出来了。
7.3 数仓方案设计题
还有一个比较综合的场景:某公司要把业务库数据同步到数仓,需要你设计一个整体方案。面试官期望得到的不是一个零散的组件列表,而是一个有逻辑的完整链路设计。
我的回答路径是:先讲数据源接入层,全量同步用批量抽取,增量同步用binlog接入;再讲存储与计算层,选择一套离线引擎做批处理、一套OLAP引擎做查询加速;再讲数据建模层,按前面聊的分层思路组织ODS、DWD、DWS、ADS;最后讲调度与运维层,调度平台、质量监控、血缘追踪、告警通知。
面积图这种题的考察点在于你能不能权衡取舍。比如问:你们为什么不用实时同步全量?合理的回答是成本太高且业务上分钟级延迟足够。再问:为什么OLAP引擎选某个而不选另一个?合理的回答是结合团队已有组件栈和数据量级决定的,同时提到可以保留备选引擎做技术调研。
从面试官的视角看,能独立完成一套链路设计的人,通常就是他们想要的数仓工程师。因为这样的候选人不仅能写SQL,还有全局视野,知道每层技术选型要解决的问题,以及整条链路的瓶颈会出现在哪里。
7.4 现场画架构图注意细节
如果面试官要求你现场画出数仓架构图,不要紧张,这基本上是个送分题,关键是你要画得有条理。
画图时先分层,从上到下依次是数据源层、数据接入层、数据存储与计算层、数据服务层、数据应用层。然后标注每层的核心组件,比如接入层的Kafka、同步工具;计算层的离线引擎和实时引擎;存储层的OLAP加速引擎;服务层的数据API和指标平台。
要把数据流向画清楚,用箭头标出数据从源端到ODS、DWD、DWS、ADS的路径,实时和离线两条链路分别用不同的箭头表示。面试官看到你确实在画“两层数仓”,而不是只有一个大数据平台,基本就达到目的了。
画完图之后,主动讲解几个关键设计点:分层带来的稳定性、数据血缘在架构中的位置、实时和离线双链路的一致性保证。不需要展开太多细节,重点是让面试官看出你对整个架构体系了如指掌,而不是只熟悉某个组件。
8. 从面试题到日常积累的转变
写这份面试题的过程里,我最大的感受是:面试题目其实是一个非常好的知识体检清单。哪怕你没有准备换工作,每隔半年拿这些题自测一遍,也能帮你找出知识体系里的薄弱点。
我个人建议的做法是,每道题不只是背答案,而是对应到你现在的工作内容中去找真实案例。比如拉链表那道题,想想你们线上的用户表是不是每天全量更新,如果是,能不能改成拉链表?数据倾斜那道题,翻翻最近跑得很慢的任务,看看是不是真有这个问题。面试答题的最高境界是,你说什么都有具体项目做支撑,而不是给一个模板化的解说。
另外,数仓技术栈迭代非常快,早期是Hive全家桶,很多团队已经切换到Spark和Flink为主,现在Iceberg和Hudi等数据湖技术也在加速渗透。面试题也会随之变化,比如事务支持、时间旅行、流批一体这些新的知识点已经频繁出现在高级别的面试中。这份题目我会定期补充和修订,也欢迎同行在评论区分享你们遇到的新的面试题。
最后说一点关于面试心态的经验。数仓工程师面试很少是八股文背诵,大多数面试官是想通过问题了解你的思考过程。遇到不会的题,坦诚说出来,然后把自己能想到的方向讲清楚,比如“这个功能我现在还没在项目中实践过,但我理解的思路可能是……”,这种态度反而比硬编一个不成熟的答案更让人信服。做我们这行,最稀缺的能力不是懂多少知识点,而是面对一个不确定的问题时,能不能有一套有条理的排查和推理路径。