1. 为什么Oracle的笔试题总让人“一看就会,一做就废”
聊Oracle笔试面试题之前,先得把一件事说透:Oracle的题目设计思路和很多互联网大厂完全不是一个路子。互联网公司偏爱算法题,考的是你临场推导的能力;而Oracle这类企业级数据库厂商的笔试,考的是你对数据库底层机制的理解深度和SQL实战的肌肉记忆。我带过几个准备Oracle面试的朋友,他们最大的感受就是——题目单看每个字都认识,但组合起来就不知道从哪下手。
这个项目标题“Oracle 笔试面试题详解”看起来像是一份题库整理,但真正有价值的部分不是题目本身,而是题目背后暴露出来的知识盲区。你做完一套题,错的那些地方,恰恰就是你平时写SQL时靠“试出来”而不是“想明白”的地方。所以这篇内容我打算换个角度来写:不按题型分类,而是按知识模块来拆,把每道典型题当成一个入口,往深了挖它考的是什么、为什么这么考、实际工作中怎么用。
适合谁来读?如果你是刚毕业准备数据库方向岗位的,这篇能帮你建立一套完整的知识框架;如果你已经工作两三年,想跳槽到更偏数据库内核或DBA方向的岗位,这篇能帮你把零散的经验串成体系。我不会只给你答案,我会告诉你出题人到底想筛掉什么样的人。
2. 笔试题目背后的知识模块拆解
2.1 SQL基础题为什么总能埋坑
先看一道很典型的题:
SELECT * FROM employees WHERE salary > (SELECT AVG(salary) FROM employees);这题看起来简单到不行,但面试官真正想考的是子查询的执行方式。很多人张口就说“先算平均工资,再逐行比较”,这个回答在逻辑上没错,但在执行层面太粗糙了。Oracle的优化器可能会把这种非相关子查询转换成一次性的标量计算,也可能在某些版本和统计信息下走FILTER操作。你要能说出“这个子查询不依赖外层行,所以理论上只需要执行一次”,这才算摸到了门槛。
再比如这道:
SELECT department_id, COUNT(*) FROM employees GROUP BY department_id HAVING COUNT(*) > 5;考的是WHERE和HAVING的区别,但真正的坑在于:如果我把条件改成WHERE department_id > 10,放在GROUP BY之前和之后,结果一样吗?很多人会愣一下。答案是逻辑上一样,但执行计划不同——放在WHERE里会先过滤再分组,放在HAVING里会先分组再过滤。数据量大的时候,这个差异就是几秒和几分钟的区别。
注意:笔试里凡是出现GROUP BY的题,先看过滤条件能不能下推到WHERE,这是最基本的优化意识。
2.2 多表连接题的隐藏考点
连接查询是Oracle笔试的重灾区。我统计过自己见过的题目,大概有三分之一都涉及多表连接。但出题人不会直接问你“写一个左连接”,而是会这样问:
“查询每个部门的名称和该部门员工的最低工资,包括没有员工的部门。”
这道题的关键词是“包括没有员工的部门”,这就是在考外连接。但更深一层,它在考你连接条件的放置位置。如果你写成:
SELECT d.department_name, MIN(e.salary) FROM departments d, employees e WHERE d.department_id = e.department_id(+) GROUP BY d.department_name;这是Oracle传统的外连接语法,加号放在哪一边是有讲究的。加号在e那边,表示e是可选表,d是主表。如果你把加号放反了,结果就完全不对。现在更推荐用ANSI标准的LEFT JOIN写法,但很多老系统里还是这种老语法,笔试也爱考。
还有一个更隐蔽的坑:连接和过滤的顺序。看这道题:
SELECT d.department_name, e.last_name FROM departments d LEFT JOIN employees e ON d.department_id = e.department_id WHERE e.salary > 5000;问:这个查询会返回没有员工的部门吗?答案是不会。因为WHERE条件在连接之后执行,e.salary为NULL的行会被过滤掉。如果你想让没有员工的部门也显示出来,必须把条件放到ON子句里:
SELECT d.department_name, e.last_name FROM departments d LEFT JOIN employees e ON d.department_id = e.department_id AND e.salary > 5000;这个区别在实际工作中极其重要,我见过不止一个工作了三四年的人在这里翻车。
2.3 事务与锁的题目怎么答到点子上
事务相关的笔试题通常以选择题或简答题出现,比如:
“以下关于事务隔离级别的描述,哪个是正确的?”
这种题如果你只背了四种隔离级别的名字,大概率会选错。因为Oracle的默认隔离级别是READ COMMITTED,但它实现的机制和标准SQL定义的READ COMMITTED有细微差别——Oracle用的是多版本一致性读,读操作不会阻塞写操作,写操作也不会阻塞读操作。这个特性在笔试里经常被拿来和别的数据库做对比。
再比如这道经典题:
“一个会话执行了UPDATE但未提交,另一个会话查询同一行数据,会看到什么?”
答案是看不到未提交的修改,因为Oracle的一致性读会从UNDO表空间里 reconstruct 出修改前的数据。但如果你追问“那第二个会话能更新这一行吗”,答案就变成了会被阻塞,直到第一个会话提交或回滚。这就是读不阻塞写、写阻塞写的典型场景。
实操心得:回答事务题的时候,一定要把“读”和“写”分开说,不要笼统地说“会锁”或“不会锁”。Oracle的锁机制是行级锁,而且没有读锁,这一点和很多人的直觉相反。
2.4 索引与执行计划的送分题与送命题
索引相关的题目,简单的很简单,难的能难倒一片。简单题比如:
“什么情况下索引会失效?”
标准答案能列一堆:在索引列上使用函数、隐式类型转换、使用NOT、使用IS NULL、前导通配符的LIKE等等。但面试官真正想听的是为什么。比如为什么在索引列上用函数会导致失效?因为B树索引存的是列的原值,你套个UPPER函数,索引里没有UPPER后的值,自然用不上。除非你建的是函数索引。
难题比如:
“一个表有100万行,索引列有大量重复值,查询返回20%的数据,走索引还是全表扫描?”
这题考的是选择率的概念。当返回数据量占比很高时,走索引反而更慢,因为要回表。Oracle的优化器会根据统计信息计算成本,但统计信息可能过时,所以有时候需要手动干预。笔试里如果问到“为什么执行计划突然变了”,八成是在考统计信息收集的问题。
-- 查看执行计划的常用命令 EXPLAIN PLAN FOR SELECT * FROM employees WHERE department_id = 50; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);注意:笔试里如果让你写执行计划分析,不要只写“走索引”或“全表扫描”,要写出访问路径和连接方式,比如“INDEX RANGE SCAN”和“NESTED LOOPS”。
3. 从题目到实战的完整推演过程
3.1 一道综合题的完整拆解
来看这道题,我把它称为“笔试里的终极缝合怪”:
“查询每个部门中工资最高的员工信息,如果最高工资有多个员工,全部列出。同时显示该部门的平均工资和员工人数。”
这道题考了至少五个知识点:分组聚合、子查询、连接、排名函数、空值处理。我一步步拆给你看。
第一步,先找每个部门的最高工资:
SELECT department_id, MAX(salary) AS max_sal FROM employees GROUP BY department_id;第二步,把这张结果表和原表连接,找出工资等于最高工资的员工:
SELECT e.*, d.max_sal, d.avg_sal, d.cnt FROM employees e JOIN ( SELECT department_id, MAX(salary) AS max_sal, AVG(salary) AS avg_sal, COUNT(*) AS cnt FROM employees GROUP BY department_id ) d ON e.department_id = d.department_id AND e.salary = d.max_sal;但这里有个坑:如果某个部门有两个人工资都是最高,这个写法会把两个人都查出来,没问题。但如果某个部门的最高工资是NULL呢?MAX会忽略NULL,但连接条件e.salary = d.max_sal在NULL的情况下不会匹配。所以更稳妥的写法是用排名函数:
SELECT * FROM ( SELECT e.*, AVG(salary) OVER (PARTITION BY department_id) AS avg_sal, COUNT(*) OVER (PARTITION BY department_id) AS cnt, RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rk FROM employees e ) WHERE rk = 1;这个写法更优雅,而且能处理NULL的情况(NULL在ORDER BY DESC里默认排最后)。但笔试的时候,面试官可能故意不让你用窗口函数,就是想看你会不会用基础语法实现同样的逻辑。
实操心得:窗口函数是Oracle笔试的加分项,但不要一上来就用。先写基础解法,再补一句“如果允许用窗口函数,可以这样优化”,这样既展示了基本功,又体现了进阶能力。
3.2 参数计算与选择率的实际推演
笔试里经常出现“该走索引还是全表扫描”的判断,很多人凭感觉答,其实是可以算的。我教你一个简化版的估算方法。
假设一个表有10000行,每行200字节,块大小8KB。全表扫描需要读的块数大约是:
10000 * 200 / 8192 ≈ 244 块如果索引列的选择率是1%,也就是返回100行。走索引需要读索引块(假设索引高度为2,需要读3个索引块)加上回表读100个数据块,总共约103个块。这时候走索引更快。
但如果选择率是20%,返回2000行,回表就要读2000个块(假设每行在不同块里),加上索引块,总共超过2000块,远大于全表扫描的244块。这时候全表扫描更快。
Oracle的优化器就是基于类似的成本计算来选择执行计划的。笔试里如果给你具体数字,你可以用这个思路去估算。如果没给数字,你就说“取决于选择率和聚类因子”,聚类因子越接近块数,索引回表的成本越低。
| 选择率 | 返回行数 | 索引成本(估算) | 全表扫描成本 | 建议 |
|---|---|---|---|---|
| 1% | 100 | 约103块 | 244块 | 走索引 |
| 5% | 500 | 约503块 | 244块 | 看聚类因子 |
| 20% | 2000 | 约2003块 | 244块 | 全表扫描 |
3.3 笔试现场的时间分配策略
Oracle笔试通常题量不小,我见过一套题有50道选择加5道SQL手写。时间分配很关键。我的建议是:
- 选择题:平均每题不超过1分钟。遇到不确定的先标记,不要死磕。很多选择题考的是细节记忆,想太久反而容易选错。
- SQL手写题:每题留8-10分钟。先写框架,再补细节。如果卡住了,先把能写的部分写上,比如FROM和WHERE,再慢慢补SELECT和GROUP BY。
- 简答题:每题5分钟。分点作答,不要写大段文字。面试官看的是关键词,不是你的文笔。
注意:如果笔试是在线平台,注意保存。我见过有人写了半小时的SQL,因为页面刷新全没了。每写几行就Ctrl+S,或者先在本地编辑器写好再粘贴。
4. 常见问题与排查技巧实录
4.1 笔试中容易混淆的概念速查表
下面这张表是我自己整理的高频混淆点,笔试前过一遍,能救回不少分。
| 混淆点 | 正确理解 | 常见错误 |
|---|---|---|
| WHERE vs HAVING | WHERE在分组前过滤行,HAVING在分组后过滤组 | 在WHERE里用聚合函数 |
| UNION vs UNION ALL | UNION去重并排序,UNION ALL不去重不排序 | 以为UNION ALL更快所以总是用它 |
| TRUNCATE vs DELETE | TRUNCATE是DDL,不可回滚,重置高水位;DELETE是DML,可回滚 | 以为TRUNCATE可以带WHERE |
| RANK vs DENSE_RANK | RANK跳号(1,2,2,4),DENSE_RANK不跳号(1,2,2,3) | 在需要连续排名时用错 |
| 隐式转换 | 字符串和数字比较时,Oracle把字符串转成数字 | 以为索引还能用 |
| NULL比较 | NULL不能用=比较,要用IS NULL | 写WHERE col = NULL |
| 外连接加号 | (+)放在可选表一侧 | 放反了导致结果集错误 |
| 序列.nextval | 每次调用都递增,回滚不恢复 | 以为事务回滚会重置序列 |
4.2 手写SQL时的排查思路
笔试手写SQL,最怕的是写完了自己不知道对不对。我教你一个自查流程:
- 看FROM:表名对不对?需不需要别名?多表连接的条件写了吗?
- 看WHERE:过滤条件全不全?有没有漏掉连接条件导致笛卡尔积?
- 看GROUP BY:SELECT里非聚合的列,是不是都在GROUP BY里?
- 看HAVING:聚合函数的过滤条件是不是放在这里了?
- 看ORDER BY:排序字段对不对?升序还是降序?
这个流程走一遍,能筛掉80%的低级错误。剩下的20%是逻辑错误,比如该用外连接的地方用了内连接,该用子查询的地方用了连接。这种就要靠平时多练了。
实操心得:笔试的时候,如果题目没要求优化,先写最直观的解法。不要一上来就炫技用窗口函数或递归查询,万一写错了,连基础分都拿不到。先保证正确,再考虑优雅。
4.3 面试官追问时的应对策略
笔试过了之后,面试官经常会拿着你的答案追问。比如你写了一个子查询,他会问“这个子查询能改成连接吗?为什么?”这时候你要能说出两者的区别:子查询更直观,但连接通常性能更好,因为优化器有更多重写空间。但也不是绝对的,相关子查询在某些情况下反而更快,因为它可以利用索引的过滤能力。
再比如你用了NOT IN,面试官会问“如果子查询结果里有NULL会怎样?”答案是整个查询返回空结果。因为NOT IN (1, 2, NULL)等价于!= 1 AND != 2 AND != NULL,而任何值和NULL比较都是UNKNOWN,所以永远不成立。这时候应该用NOT EXISTS或者把NULL排除掉。
这种追问环节,考的是你的知识边界。你知道就知道,不知道就说“这个我平时用得少,我的理解是……但不确定对不对”。千万不要硬编,面试官一眼就能看出来。
5. 从笔试到工作的能力迁移
5.1 笔试题目在实际工作中的影子
很多人觉得笔试题目脱离实际,其实不是。我举几个例子。
笔试里常考的“查询每个部门工资最高的员工”,在实际工作中就是“查询每个品类销量最高的商品”、“查询每个客户最近一笔订单”。这种分组取Top N的需求,在报表系统里天天出现。
笔试里常考的“事务隔离级别”,在实际工作中就是“为什么我的查询看不到别人刚提交的数据”、“为什么我的更新被阻塞了”。不理解隔离级别,遇到并发问题就只能重启数据库。
笔试里常考的“索引失效”,在实际工作中就是“为什么这个查询昨天还好好的,今天突然变慢了”。很多时候就是因为开发在索引列上套了个函数,或者传参类型不对导致了隐式转换。
所以我的观点是:笔试题目不是用来为难你的,是用来帮你建立排查问题的思维框架的。你做题的时候多想一步“这个知识点在实际中怎么用”,比单纯刷题效果好得多。
5.2 建立自己的知识检查清单
我建议每个准备Oracle面试的人,都建一个自己的检查清单。不用很复杂,就按模块列:
- SQL基础:SELECT执行顺序、NULL处理、类型转换、CASE WHEN
- 连接:内连接、外连接、自连接、交叉连接、连接条件与过滤条件的区别
- 分组:GROUP BY规则、HAVING与WHERE、ROLLUP与CUBE
- 子查询:相关与非相关、IN与EXISTS、ANY与ALL
- 窗口函数:RANK、ROW_NUMBER、LEAD、LAG、SUM OVER
- 事务:ACID、隔离级别、锁机制、MVCC
- 索引:B树结构、选择率、聚类因子、函数索引、复合索引最左匹配
- 执行计划:访问路径、连接方式、成本计算、统计信息
每次做题或者面试前,过一遍这个清单,哪个模块心里没底就重点补。这个方法我用了好几年,比漫无目的地刷题效率高得多。
5.3 一个容易被忽略的加分项
最后说一个很多人忽略的点:笔试时的书写规范。SQL关键字大写、缩进对齐、别名有意义、注释清晰。这些细节不会直接加分,但会给面试官留下“这个人写代码很规范”的印象。尤其是手写SQL的时候,字迹工整、逻辑分层清晰,面试官看着舒服,可能就多给你几分。
我见过一个候选人,SQL写得完全正确,但所有关键字小写、没有缩进、别名用a、b、c,面试官看完说了一句“这代码没法维护”。虽然最后还是给了过,但评价明显打了折扣。所以别小看这些细节,它们是你专业度的直接体现。
实操心得:平时练习的时候就用规范格式写,考试的时候才不会临时改习惯。我习惯把SELECT、FROM、WHERE、GROUP BY、ORDER BY各占一行,缩进用两个空格,别名用表名的缩写。这个习惯保持了五六年,受益良多。
6. 几个我踩过的坑和总结的经验
第一个坑:过度依赖窗口函数。有段时间我做什么都用ROW_NUMBER(),结果有一次面试,面试官明确说“不能用窗口函数”,我当场卡壳。后来我逼自己用基础语法实现同样的逻辑,才发现很多窗口函数的底层就是自连接和分组聚合。这个反向练习让我对SQL的理解深了一大截。
第二个坑:忽略执行计划。以前做题只看结果对不对,不看怎么执行的。后来工作中遇到一个查询,逻辑完全正确但跑了半小时,一看执行计划发现走了笛卡尔积。从那以后,我每写一个复杂SQL,都会先EXPLAIN一下,看看有没有全表扫描或者嵌套循环过多的问题。
第三个坑:死记硬背隔离级别。四种隔离级别的定义我背得滚瓜烂熟,但面试官问“Oracle默认是哪个,为什么”的时候,我答不上来。后来才知道Oracle的READ COMMITTED是基于多版本一致性读实现的,和标准SQL的定义有差异。这个知识点后来在面试中被问到过三次,每次都能拉开差距。
最后一个经验:把每次笔试当成学习机会。不要做完题对完答案就扔了,把错题整理出来,找到对应的知识点,写一篇自己的理解。我当年准备面试的时候,整理了大概五十道错题,每道题都写了“考什么、为什么错、正确思路、实际应用”。这份笔记后来成了我面试前唯一的复习资料,比任何题库都管用。
这个内容后续还可以这样扩展:把每道题的实际执行计划跑出来,对比不同写法的性能差异;或者把题目改造成实际工作场景,比如“订单表里查询每个客户最近一笔订单”,然后讨论分区表和索引的设计。这些方向都比单纯刷题有意思得多。