2026/10/13 7:33:43

大数据开发SQL面试高频考点与优化实战解析

大数据开发SQL面试高频考点与优化实战解析 1. 为什么SQL是大数据开发面试的“生死线”做了这么多年大数据开发面试过不少人也被面试过不少次一个很真实的感受是SQL这关过不了其他都白搭。你Spark写得再溜、Flink状态管理理解得再深落到面试官面前他大概率还是会先问你几道SQL题探探底。原因很简单大数据开发日常工作中SQL是使用频率最高的语言。离线数仓用HiveSQL实时计算用FlinkSQL即席查询用Presto/ClickHouse底层的HBase、Kafka虽然不直接用SQL但周边生态的工具多多少少都带SQL影子。可以说SQL就是大数据开发者的通用语言面试官用SQL题来筛选候选人成本低、区分度高、可考察维度广。很多候选人有个误区觉得SQL就是写个select、join、where能跑出结果就行。但面试官真正想考察的是你对SQL执行逻辑的底层理解、对数据处理的思维模式、以及对性能优化的敏感度。同一个需求有人写出来的SQL跑半小时有人改写后三分钟跑完有人面对“取每个部门薪资最高的员工”这种题能想到三种写法并分析优劣有人只会一种写法还说不清原理——这就是差距。这篇文章围绕面试中出现频率最高的几类SQL考点展开in/exists的语义区别与执行逻辑、having/where的过滤时机差异、排序函数的进阶用法、以及常考的实际场景题。内容是结合我这些年面试与被面试的经验整理的题目都是高频题解析部分既讲答案也讲为什么是这个答案争取让你看完之后不但会做题还能在面试现场把思路讲清楚给面试官留下“这人是真懂SQL”的印象。2. 面试前必须吃透的四个SQL核心概念2.1 in与exists不只是“用哪个”的问题in和exists是面试题里的常客几乎每三场面试就会遇到一次。很多人背过结论“小表驱动大表用in大表驱动小表用exists”但这个结论本身是片面的而且很容易让你在面试官的追问下露馅。先看语义层面的区别。in是匹配判断它的逻辑是外层查询的某个字段值是否出现在内层查询返回的集合中。比如select * from emp where dept_id in (select dept_id from dept where location 北京);exists是存在性判断它的逻辑是对于外层查询的每一行去执行内层子查询如果子查询有返回结果则这一行保留。写法上有一个容易被忽略的细节exists子查询的select列表理论上写什么都行因为它根本不关心返回了什么列只关心有没有返回行select * from emp e where exists (select 1 from dept d where d.dept_id e.dept_id and d.location 北京);这里select 1是习惯写法写select *也没错但容易让人误解以为exists会取列值实际上它只看是否存在满足条件的行。面试官最爱追问的点是这两者的执行效率哪个高这就涉及执行计划的差别。经典的逻辑是in会先执行子查询把结果集物化成一个临时表然后外层查询与这个临时表做连接exists则是典型的“外层驱动内层”每处理一行外层数据就去执行一次内层子查询。基于这个逻辑才有了“外层结果集小用exists、内层结果集小用in”的说法。但这里必须补充一个重要认知现代的大数据引擎和关系型数据库优化器早就不是按教科书逻辑执行了。Hive从0.8版本左右开始支持in和exists的语义转换SparkSQL的优化器会在物理执行前对逻辑计划做大量改写很多情况下in和exists会被改写成同一种Join策略。所以面试时如果只谈“exists永远比in快”或者反过来反而暴露了你对优化器了解不够。更稳妥的回答思路是从语义出发选型——如果内层子查询的结果集可能包含NULLin的行为会变得微妙见后面的坑点从性能角度表态——具体哪个快取决于优化器、数据规模和数据分布不能一概而论但我们可以通过执行计划explain来判断实际执行策略。这个回答既展现了理论功底又体现了工程意识面试官很难挑毛病。再补充一个实际工作中常用的经验当in和exists都能表达同一需求时我更倾向于用in。原因一是可读性好二是大多数引擎对in的优化支持比较成熟三是exists的嵌套写法在复杂SQL中容易造成阅读负担。但要注意控制in后面的集合大小几万个元素的in列表会让很多引擎的优化器直接放弃治疗这时候就该换成join或者拆分成临时表。2.2 having与where一字之差天壤之别having和where的考点比较基础但恰恰是基础才最能看出功底。很多候选人能说“where是过滤行having是过滤分组”但说不清楚深层的原因。面试官追问一句“为什么聚合函数不能用在where里”就卡住了。核心区别在于执行时机。where是在分组和聚合之前执行的它作用于原始数据行having是在分组和聚合完成之后执行的它作用于分组后的结果集。这就解释了为什么where里不能写聚合函数因为where执行的时候分组还没发生聚合值根本还没计算出来。你不可能在结果还没算出来之前就去过滤它。举一个经典的例子查询部门平均薪资大于8000的部门select dept_id, avg(salary) as avg_sal from emp group by dept_id having avg(salary) 8000;如果把having换成where写成where avg(salary) 8000大多数引擎会直接报错告诉你无效使用聚合函数。即使个别引擎对这种写法做了兼容逻辑上也是错误的因为过滤时机不对。还有一个隐藏考点很多人没注意where和having共存时的执行顺序。看这个SQLselect dept_id, count(*) as cnt from emp where salary 3000 group by dept_id having count(*) 10;它的执行逻辑是先过滤掉薪资低于3000的员工然后按部门分组计数最后只保留人数大于10的部门。也就是说where过滤后的数据参与了聚合having过滤的是聚合结果。如果这个需求写成where salary 3000且having count(*) 10中间被过滤掉的低薪资员工不参与任何聚合计算这会影响count的结果——很多人没意识到这个连带效应。再延伸一个点能不能用where替代having在特定场景下可以比如过滤条件不涉及聚合函数且分组字段就是原始列时select dept_id, count(*) from emp group by dept_id where dept_id A;这个写法有语法问题正确的做法是先用where过滤再分组select dept_id, count(*) from emp where dept_id A group by dept_id;这里就体现出写SQL的思维方式能用where提前过滤的尽量不要拖到having。因为提前过滤能减少进入分组操作的数据量对性能有明显的正面影响。这个点如果能在面试中主动讲出来就是加分项——说明你不只是会写SQL还关心SQL的执行效率。2.3 排序函数三兄弟row_number、rank、dense_rank排序函数是面试题的高频考点尤其是“取分组TopN”这类需求几乎必考。很多候选人知道有三个排序函数但区别说不清楚或者动手写的时候用错函数导致结果不对。这三个函数的区别用一个例子就能说透。假设有四个人的薪资分别是10000、9000、9000、8000row_number()不管有没有并列都按顺序编号结果是1、2、3、4。rank()有并列时占用名次相同值的行并列同一个名次但下一个名次会跳号结果是1、2、2、4。dense_rank()有并列时也并列名次但下一个名次不跳号结果是1、2、2、3。记住一句话rank跳号dense_rank不跳号row_number唯一编号。这个考点在面试中不光会以理论题形式出现还会包装成“取每个部门薪资排名前3的员工”“按班级成绩排名并显示并列名次”等场景题。这三个函数都属于窗口函数。窗口函数的执行顺序也经常被问到要记住一个大原则窗口函数是在where、group by、having之后执行的也就是说窗口函数能使用的数据是“过滤完、聚合完”之后的数据。这带来一个很常见的坑如果你想对分组聚合后的结果再排序取TopN直接用where去过滤窗口函数的结果是行不通的因为where的执行时机早于窗口函数。比如这个错误写法select dept_id, salary, row_number() over (partition by dept_id order by salary desc) as rn from emp where rn 3;这个SQL在绝大多数引擎里会直接报错因为rn这个别名是在select阶段生成的而where执行得更早根本不知道rn是什么。正确做法是套一层子查询select dept_id, salary, rn from ( select dept_id, salary, row_number() over (partition by dept_id order by salary desc) as rn from emp ) t where rn 3;这个“子查询包一层再过滤”的手法是TopN类题目的标准解法一定要熟练。面试时写出这种结构并且解释清楚为什么要套子查询就说明你真正理解窗口函数的执行顺序。2.4 group by与聚合函数的执行逻辑group by是SQL面试的另一个基础考点单独拿出来说是因为很多人在复杂场景题中栽在group by的理解上。group by的核心逻辑是按照指定的字段把多行数据合并成若干个分组每个分组内的多行数据被压缩成一行输出。压缩的过程中select列表中出现的字段只有两种group by中的字段或者被聚合函数包裹的字段。其他字段如果想出现在select里必须也写在group by中否则就是非法SQL。这个规则是SQL的语法规定底层逻辑是分组后每个组只剩一行非分组字段有多行值引擎不知道该取哪一行。有一个容易踩坑的点是有些引擎比如MySQL的部分模式下允许select非分组字段返回的是组内的随机行数据。这在面试时如果被问到要能明确指出这是不规范写法生产环境中应该避免因为结果不可控。聚合函数与group by配合使用时有一个顺序问题需要记住先分组后聚合。聚合函数作用于每个分组内的所有行。也就是说count(*)统计的是每个分组内的行数sum(salary)计算的是每个分组内的薪资总和。理解了这个就能明白为什么having可以直接使用聚合函数——因为having执行时聚合结果已经计算出来了。另外很多人在写“分组后取组内某一行”这类需求时会不自觉地用group by但实际group by并不适合这个场景。比如取每个部门薪资最高的员工目标是“每个部门返回一条记录且这条记录是薪资最高的那个人的完整信息”。如果用group byselect列表里除了dept_id和max(salary)其他字段要么不能出现要么出现但取值不确定。这种需求的标准解法是窗口函数或者自连接而不是group by。这个区分非常关键面试官出这种题就是想看你能不能选对工具。3. 面试必考的高频SQL场景题与手写解析3.1 场景题一取每个部门薪资最高的员工TopN问题这是大数据开发面试中出现频率最高的一道题没有之一。某公司、某大厂无论大小厂都喜欢出这道题因为解法多样、可深挖的点多。我在面试中让候选人写这道题最少见过三种写法每种写法的优劣能直接反映候选人的SQL水平。先看标准解法用窗口函数select dept_id, emp_name, salary from ( select dept_id, emp_name, salary, row_number() over (partition by dept_id order by salary desc) as rn from emp ) t where rn 1;这个解法的核心逻辑是先按部门分组在组内按薪资降序编号然后取编号为1的那一行。这里用的是row_number而不是rank或dense_rank因为需求是“薪资最高”如果有两个人并列最高row_number会随机取一个rank会返回两行——具体用哪个取决于是否允许并列这在需求分析时就要问清楚。面试时主动问“如果薪资并列怎么处理”是一个加分的提问。第二种解法是用自连接思路是找出“不存在薪资比我高的人”的那些记录select e1.dept_id, e1.emp_name, e1.salary from emp e1 left join emp e2 on e1.dept_id e2.dept_id and e2.salary e1.salary where e2.emp_id is null;这个解法的逻辑很巧妙把自己和同一部门薪资更高的人做连接如果能连接上说明存在比我薪资高的人那我不可能是最高连接不上e2为空的就是部门里的最高薪资。不过这个解法在大表场景下性能比较差因为自连接会产生大量的中间数据而且如果两张表的数据量都很大这个join的成本会很高。第三种解法是通过group by加子查询select dept_id, emp_name, salary from emp where (dept_id, salary) in ( select dept_id, max(salary) from emp group by dept_id );先按部门求最大薪资再反查哪些员工的部门与薪资对上。这个解法的可读性不错但有一个隐藏问题如果同一部门有两个人薪资相同且都是最高这两个人都会被查出来。在需要唯一记录的场景下这个解法需要额外处理。面试时推荐优先展示窗口函数解法因为结构清晰、性能可控、可扩展性强。能从这种题延伸到“每个部门薪资前3名”只需要把where rn 1改成where rn 3说明你对这个考点有完整的理解。3.2 场景题二连续登录天数问题经典连环追问连续N天登录问题是面试官非常喜欢的进阶题难度不小考察的是窗口函数lag/lead、日期处理、以及分组思想。这道题第一次接触可能会卡壳因为直观上不太好确定“连续”怎么判断——直接按用户分组数日子的话中断的日子没法处理。标准解法是“行号差值法”。先看核心步骤select user_id, d, date_sub(d, rn) as grp from ( select user_id, log_date as d, row_number() over (partition by user_id order by log_date) as rn from login_log ) t;这里的思路是如果用户的登录日期是连续的那么日期减去行号得到的值应该相同。比如用户A在1号、2号、3号登录行号分别是1、2、3日期减行号分别是0、0、0同一个值如果1号登录、3号登录行号是1、2日期减行号是0、1值不同。这样就把“连续区间”的识别转化成了“同一分组”的问题。然后在此基础上统计每组的天数select user_id, grp, count(*) as continuous_days from ( select user_id, log_date, date_sub(log_date, rn) as grp from ( select user_id, log_date, row_number() over (partition by user_id order by log_date) as rn from login_log ) t1 ) t2 group by user_id, grp;再求每个用户的最大连续天数再套一层select user_id, max(continuous_days) as max_days from ( select user_id, grp, count(*) as continuous_days from ( select user_id, log_date, date_sub(log_date, rn) as grp from ( select user_id, log_date, row_number() over (partition by user_id order by log_date) as rn from login_log ) t1 ) t2 group by user_id, grp ) t3 group by user_id;面试中写这道题时关键是讲清楚“日期减行号”这个核心思想。我在实际工作中第一次遇到这个需求时也是想了半天——直觉上想用lag或lead判断前后日期是否相邻但那样写出来的逻辑很复杂而且如果连续区间多代码会非常啰嗦。行号差值法把复杂问题转化成了分组统计问题代码简洁而且逻辑清晰。另外要注意日期函数的差异。上面示例用的是Hive风格的date_sub函数MySQL里是date_sub(log_date, interval rn day)Oracle是log_date - rn。面试时可以说明自己最熟悉哪种引擎的语法并指出迁移时需要注意日期函数的差异这会让面试官觉得你有跨引擎开发经验。3.3 场景题三找出连续出现3次及以上的数字这是一道在数据库面试领域流传很久的经典题核心考察点是表自连接或者窗口函数lag。题目大概是有一个表记录了某个数字序列要求找出连续出现至少3次的值。教科书解法是用三表自连接select distinct l1.num from log l1, log l2, log l3 where l1.id l2.id - 1 and l2.id l3.id - 1 and l1.num l2.num and l2.num l3.num;这个解法的逻辑直白找三行相邻的记录且数字相同。但缺点也很明显——如果问题改成“连续出现5次”就要写五表自连接非常笨拙。更好用的是lag窗口函数解法select distinct num from ( select num, lag(num, 1) over (order by id) as prev1, lag(num, 2) over (order by id) as prev2 from log ) t where num prev1 and num prev2;lag函数可以往前取任意偏移量的值把“往前看两次”的逻辑直接写出来。改成“连续出现5次”只需要增加lag(num, 3)、lag(num, 4)两个列条件改成num等于四个prev即可。这道题的难点其实不在语法而在思维方式。很多人第一次看到这个题会想按num分组但按num分组后id的顺序信息就丢了没法判断“连续”。正确的思路是用id排序然后往前看N行做比较。面试中如果你能指出“分组不行必须保序”这个关键点面试官会非常认可——说明你抓住了问题的本质。3.4 场景题四in与exists的实战对比既然前面花了很大篇幅讲in和exists这里给一道典型的实战题来检验你的理解。题目背景有两张表orders是订单表字段order_id, cust_id, order_date, amountcustomers是客户表字段cust_id, cust_name, level。需求是查出所有在2024年下过订单且订单金额大于500元的客户信息。exists写法select c.* from customers c where exists ( select 1 from orders o where o.cust_id c.cust_id and o.order_date between 2024-01-01 and 2024-12-31 and o.amount 500 );in写法select c.* from customers c where c.cust_id in ( select cust_id from orders where order_date between 2024-01-01 and 2024-12-31 and amount 500 );两条SQL结果一样执行逻辑有差异。exists是逐行驱动in是先生成子查询结果集再匹配。前面说了现代优化器可能会改写执行计划但有一个本质区别需要注意exists对NULL的处理更安全而in遇到子查询结果包含NULL时匹配逻辑会变得不可预期。实际场景中我喜欢用in因为可读性好而且大多数引擎对in的转换优化已经非常成熟。如果数据量级很大比如orders表日增量上亿这类SQL在大数据引擎里跑in和exists最终大概率都会被优化成同一种join方式。但如果面试官追问性能要说清楚具体看执行计划建议用explain确认有没有走广播join、有没有数据倾斜风险而不是背结论。4. 大数据场景下的SQL优化分析4.1 数据倾斜在SQL中的表现与应对大数据开发面试里SQL题写到后面面试官一定会往数据倾斜这个方向引导。数据倾斜是分布式计算中数据分布不均导致某个任务执行时间远大于其他任务、拖慢整个作业的现象。它可以说是大数据开发岗位上最核心的调优话题之一。在SQL层面数据倾斜最常见于group by和join操作。以group by为例如果某个key的值特别多比如订单表里某个商品的订单量占了总量的90%按这个key分组聚合时处理这个key的任务就要处理90%的数据直接拖垮整体。join场景更明显大表关联小表时如果关联键分布不均个别任务的数据量会远超平均水平。面试中问到数据倾斜很多候选人能说出“加盐”这个词但说不清完整方案。加盐的思路是给倾斜的key打散让原本集中在一个任务的数据分散到多个任务处理。具体SQL写法分两步——第一步先找出可能倾斜的key比如在Hive里可以用select key, count(*) as cnt from big_table group by key order by cnt desc limit 10;第二步是对这些倾斜key做加盐处理比如给key拼接一个随机数后缀select concat(key, _, floor(rand() * 10)) as salted_key, count(*) from big_table where key in (select key from t_skew_keys) group by salted_key;加盐后需要在结果上再做一次去盐聚合。这个方案的核心是把大key拆成10个随机小key每个小key的任务量降为原来的十分之一但要注意加盐之后需要额外的步骤还原。面试时如果能主动提到Hive对这个场景有一个更简单的参数开关比如数据倾斜聚合开关处理group by倾斜时可以将部分数据在map端先做部分聚合效果会更好——说明你不光知道原理还了解实际生产中的工具手段。4.2 join优化广播join与map joinSQL性能优化还有一个高频考点就是join方式的选择。面试官会问“大表关联小表有什么优化手段”“什么是广播变量”。核心思想不难如果一张大表和一张小表做join把大表广播到所有节点、代价太大反过来把小表广播到每个节点每个节点都在本地完成join可以避免shuffle。这个思想对应到技术实现是Spark里的broadcast join、Hive里的map join。写法上在Hive中可以通过强制提示map join hint把join变成map端join在SparkSQL里优化器会根据表的大小自动选择广播join也可以通过参数调整广播阈值。写SQL时的实践建议是把需要广播的小表放在join的右侧。因为很多引擎对join顺序是敏感的虽然新一代优化器大多能自动调整但养成这个习惯没有坏处。更重要的是要能识别什么样的表适合广播——维度表部门表、地区表、产品表通常是小表事实表是大表雪花模型和星型模型里的常见join场景基本都是事实表关联维度表这就是天然的广播join场景。另外一个容易被忽略的优化点是join之前先做过滤和字段裁剪。比如关联两张表时与其把两张表的全部字段都select出来再过滤不如在子查询里先做好过滤和裁剪减少进入join的数据量。很多人写SQL不注重这一步导致join的输入数据量比实际需要的大很多作业跑得慢还找不到原因。4.3 从执行计划反推SQL问题在实际工作中遇到SQL跑得慢我的第一反应永远是先看执行计划而不是凭感觉改SQL。这个习惯在面试中如果展现出来会是一个很大的加分项。在Hive里执行explain select ...可以看到执行计划在SparkSQL里是explain or extended在MySQL里是explain。通过执行计划能看出join是BroadcastHashJoin还是SortMergeJoin每个stage的输入数据量有没有出现不必要的shuffle过滤条件下推是否生效等。举个例子我排查过一个SQL慢的问题业务方说“这个SQL之前跑几分钟现在要跑一小时”。打开执行计划一看发现关联键上有一个函数包裹join条件变成了on substr(a.key, 1, 5) b.key相当于给关联键做了函数变换导致优化器无法使用高效的join方式只能每条数据都做一次函数计算再回表匹配性能断崖式下降。这是非常典型的隐藏问题代码审查和普通日志都发现不了只有看执行计划才能定位到。面试中如果被问到“SQL优化的一般步骤”比较完整的回答是先explain看执行计划确认有没有全表扫描、shuffle是否过多、join方式是否合理再根据执行计划反推问题可能是join键分布不均、可能是过滤条件下推失败、可能是join方式不优最后针对问题选择方案。这个回答既展示了方法论又体现了实际经验。4.4 分区分桶与谓词下推面试中另一个必然出现的优化考点是分区表的原理。分区的本质是物理存储上的目录划分水平切分数据查询时通过分区裁剪只读需要的目录大幅减少扫描量。在Hive里建分区表的标准写法是create table partitioned_table ( id bigint, name string ) partitioned by (dt string) stored as parquet;查询时指定分区条件就可以让引擎只扫描对应分区目录下的数据。这个机制对应到大数据面试核心考点是“谓词下推”predicate pushdown——优化器会把where条件尽可能下推到数据扫描阶段让引擎在读取数据时就过滤掉大部分数据减少shuffle阶段的数据量。但有一个实际使用中的常见坑如果在where条件里对分区字段做了函数处理比如where substr(dt, 1, 6) 202401很多引擎就无法把这个条件下推成分区裁剪了因为优化器识别不出你是想过滤某个分区它只能认为你是在对所有分区做完函数计算后再过滤。结果就是全分区扫描性能灾难。正确写法是直接写成where dt 202401 and dt 202402保留字段的原始形态让优化器可以准确裁剪分区。这类问题面试时考察的不是你背不背得住命令而是你有没有在生产环境里踩过坑、有没有形成“保留字段原始形态以便下推”的肌肉记忆。5. 现场面试的答题套路与准备策略5.1 拿到SQL题的五个思考步骤面试现场写SQL和平时工作写SQL有很大区别有时间的压力、有面试官的目光注视、有被打断追问的风险。很多候选人一紧张拿到题就开始写结果写了一半发现思路错了又划掉重来给面试官的印象非常差。经过我多次模拟面试和真实面试的经验总结出一个拿到SQL题后的标准思考流程照着做可以最大程度避免草率动笔第一步确认需求。把题目用自己的话复述一遍尤其关注边界条件——需要去重吗并列怎么处理时间范围是什么如果题目描述有歧义一定要当场问清楚。面试官不会因为提问扣分反而会觉得你是一个负责任的人。第二步确定核心算子。这个需求本质上是在做什么是过滤、聚合、排序还是join很多时候题目会把多个需求混合在一起你需要拆解出主算子。第三步确定关键字段和分组粒度。按什么字段分组输出哪些字段这个决定了group by写什么、select放什么。第四步选择SQL结构。是单表查询、多表join、还是子查询嵌套需要窗口函数还是自连接在动笔之前心中应该有一个完整的结构框架。第五步动笔写边写边讲思路。面试现场最忌讳闷头写、写完才说。正确方式是边写边简单说明“我先用窗口函数按部门排序然后外面包一层过滤取第一条”。这样即便写到一半出错面试官也知道你的整体思路是对的。5.2 被追问时如何应对面试官在你写完SQL后几乎一定会追问。追问的水平一般有两种一种是顺着你的答案深挖比如你用row_number写了TopN他会问“如果薪资并列怎么办”另一种是抛出反例或极端场景比如“如果同一用户每天登录多次怎么办”。应对追问的底层策略是不要急着回答先理解追问背后的意图。比如问“如果薪资并列怎么办”背后的意图是考察你是否知道rank和dense_rank的区别、是否能根据需求选择正确的函数。这时候不要只说改成rank就行了而是说明并列情况下没有明确需求时的默认处理方式以及如何通过增加次级排序字段来稳定排序结果row_number() over (partition by dept_id order by salary desc, emp_id asc) as rn再比如“每天登录多次”的追问意图是考察你处理数据时有没有考虑数据质量。正确回应是先确认原始数据中的log_date是日期还是时间戳如果是时间戳需要先格式化成日期或者用to_date函数做转换然后在分组前先对用户和日期去重因为连续登录天数关注的是“天数”不是“次数”。对于自己不确定的问题诚实说不知道但可以补一句“但如果在生产环境中我一般会通过...来验证”或者“让我想一想是否可以这样理解”。面试官讨厌的不是你不会而是你乱编。5.3 面试前SQL复习清单最后整理一个面试前三天可以照着过一遍的复习清单第一窗口函数要熟练。row_number、rank、dense_rank、lag、lead、sum() over()这六个必须能手写出来并且清楚各自在什么场景下使用。取TopN用row_number连续次数用lag累计值用sum() over()。第二group by的语法规范和聚合函数的配合要吃透。知道select列表为什么只能放分组字段或聚合字段知道having和where的使用时机。第三in/exists的语义区别以及子查询中NULL的影响。能说出各自执行逻辑的不同不要只说“谁快谁慢”。第四join的类型和区别——inner join、left join、right join、full join的结果集特征要一清二楚。尤其left join在on里写过滤条件和在where里写过滤条件的结果差异这是一个高频陷阱题。第五掌握四到五道经典场景题的多种解法。连续登录天数、分组TopN、行列转换、累计求和、相邻记录差这几个题型覆盖了大部分SQL面试题的变化。第六准备好一个“慢SQL优化实例”的讲述材料。面试官问“你有没有优化过慢SQL”如果你能按“现象-排查-定位-解决-结果”的叙事结构讲出一个完整案例这个印象分会非常高。我一直跟身边准备面试的朋友说SQL面试题不要靠背题要理解每一种语法背后的数据操作逻辑。你真正理解了一个机制题目怎么换都能应对你只是背了答案面试官换一个问法就暴露了。这篇文章里的内容如果你能自己动手把每个例子跑一遍再基于这些例子扩展出两三个自己的变体题目面试前能做到这个程度SQL关基本稳了。最后再分享一个小习惯日常工作中多留意自己写过的SQL慢的、快的、写得别扭的都复盘一下“为什么”和“怎么改”。积累到一定程度面试题在你眼里就不再是考题而是老朋友。