
2018年那次网易实习生招聘笔试数据库开发实习生方向我到现在还留着当时的草稿纸。倒不是考得多惨而是那张卷子让我意识到数据库开发的校招笔试考的不是“会不会背SQL语法”而是“在真实业务里能不能把数据玩明白”。后来几年我也帮部门出过类似的笔试题发现考察范围始终没跳出四个圈——SQL书写、原理理解、表结构设计、性能优化。这篇文章就把这四块拆开结合当年的题型方向和常见的变体讲清楚每类题到底在考什么、怎么答才不丢分。无论你是正在投实习还是帮朋友改简历都会有点用。1. 网易这场笔试的命题思路四种能力模型决定题目分布1.1 从岗位JD反推笔试考点数据库开发实习生这个岗位在网易的JD里通常会写参与数据库相关组件的开发与维护、负责业务数据存储方案设计、支撑线上SQL的性能优化。注意它不是DBA不是天天调参数更不是用Access做个小报表。它更偏向“数据库方向的开发”所以笔试题会同时考察两条线一是通用开发功底二是数据库专项知识。我见到的笔试一般分四类SQL手写、原理选择/判断、数据库设计、性能优化。具体来说SQL手写题占大头考察JOIN、聚合、子查询、窗口函数原理题考察事务、索引、锁、隔离级别设计题给一个业务场景让你画ER图或写建表语句优化题给一条慢SQL让你分析原因并改写。这四类背后对应四种能力写得出、讲得清、设计得合理、优化得到位。通篇考的就是你能不能成为一个“靠谱的数据库开发”。所以复习时别一头扎进细节先按这四个维度搭框架再往里面填知识点效率会高很多。1.2 笔试的时间分配与答题策略我根据当时回忆和一些同学的反馈大致还原一下题量选择题约10道SQL题约3到4道设计题1道优化题1道总时长90到120分钟。这种情况下时间分配很重要。我的建议是选择题控制在20分钟内SQL题每道控制在10分钟以内设计题20分钟优化题20分钟剩下时间检查。选择题里经常埋伏一些“背了就会、不背就懵”的概念比如事务隔离级别、索引失效场景这些如果没准备好会卡很久。我的策略是先做一个标记等SQL题写完了再回头来抠因为SQL题分值更高且更看熟练度。设计题和优化题如果没思路至少要写出一个带主键和外键的表结构、写出EXPLAIN的关键字段哪怕不完整也能拿到步骤分。笔试不是要求满分而是要保证基础题全对难题有思路。2. SQL手写题JOIN、聚合、窗口函数三个高频考点的实战拆解2.1 为什么这几个考点出镜率最高数据库开发日常工作中SQL是基本功。笔试考JOIN是因为业务数据几乎都是多表关联考GROUP BY和HAVING是因为统计报表天天要用考窗口函数是因为排名、同环比、分组TopN这类需求太常见。我见过有些同学临时抱佛脚能默写单表查询一遇到三表JOIN就慌本质上是对JOIN的执行逻辑不熟。记住一句话JOIN不是把表拼在一起而是对两个集合做匹配匹配不上的记录怎么处理决定了你用INNER JOIN还是LEFT JOIN。另外GROUP BY这几年考得越来越细。以前只要记住“分组后只能查分组列和聚合函数”就行现在会考你“为什么MySQL 5.7之后SELECT的非聚合列必须出现在GROUP BY里”。这个其实和SQL标准的SQL Mode有关ONLY_FULL_GROUP_BY开启以后不满足规则的SQL直接报错。笔试里如果遇到这种题别慌就说清楚这是为了消除语义歧义分组后每一组有多行不在GROUP BY里的列到底取哪一行数据库不知道干脆不让你查。2.2 真题风格模拟学生选课成绩统计我拿一套经典的学生选课场景来演示这种题在笔试里特别常见改个表名就能换个壳。表结构如下student(student_id, student_name, class_name)course(course_id, course_name)score(student_id, course_id, score)第一题查询每个班级每门课的平均分按班级和平均分排序。SELECT s.class_name, c.course_name, AVG(sc.score) AS avg_score FROM student s JOIN score sc ON s.student_id sc.student_id JOIN course c ON sc.course_id c.course_id GROUP BY s.class_name, c.course_name ORDER BY s.class_name, avg_score DESC;这里要提醒GROUP BY后SELECT里的非聚合字段MySQL 5.7.5以后必须全部出现在GROUP BY中或者被聚合函数包裹。所以用s.class_name而不是s.student_id否则8.0会直接报错5.7会随机取值。这个点笔试里很喜欢拿来挖坑。第二题查询平均成绩大于80分的学生姓名和平均分。SELECT st.student_name, AVG(sc.score) AS avg_score FROM student st JOIN score sc ON st.student_id sc.student_id GROUP BY st.student_id, st.student_name HAVING AVG(sc.score) 80;很多人在这个题上把HAVING写成WHERE AVG(sc.score) 80然后报错。WHERE是在分组前过滤行不能使用聚合函数HAVING是在分组后过滤组。要区分清楚。这个区分是笔试的高频考点也是实际开发中写统计SQL最容易踩的坑。第三题查询每门课程成绩都大于80分的学生。翻译一下就是一个学生的最低分大于80。SELECT st.student_name FROM student st JOIN score sc ON st.student_id sc.student_id GROUP BY st.student_id, st.student_name HAVING MIN(sc.score) 80;如果学生没有成绩可以被过滤掉因为JOIN默认内连接。如果要求没成绩也算“都大于80”就需要LEFT JOIN后再处理NULL但一般场景不用这么钻牛角尖。这类题还可以变形成“至少两门课大于90分”“统计挂科超过两门的学生”核心都是GROUP BY HAVING的组合。第四题用窗口函数查每个班级成绩前两名的学生姓名和分数。SELECT class_name, student_name, score FROM ( SELECT s.class_name, s.student_name, sc.score, ROW_NUMBER() OVER (PARTITION BY s.class_name ORDER BY sc.score DESC) AS rn FROM score sc JOIN student s ON sc.student_id s.student_id ) t WHERE rn 2;如果笔试环境不支持窗口函数比如MySQL 5.6替代写法是自连接或用用户变量但窗口函数是趋势最好熟练掌握。窗口函数看起来高级实际理解成“分组后不合并行而是对每一行打一个排名标记”就行。笔试里遇到查TopN或者分组排名的题第一反应就该是ROW_NUMBER()。2.3 写SQL的检查清单与常见扣分点我在帮人改笔试题时发现几个高频扣分点分号没写。在线笔试一般不会因为这个判错但手写题印象分会差。字段名不加反引号导致和关键字冲突。JOIN时ON条件写错比如把student_id写成id。排序字段没用别名或者用别名排序时方言兼容性问题。分组查询时SELECT了不相关的字段这在MySQL 5.7以前能跑、但结果可能不对。子查询不加别名这是最low的报错。写成SQL后建议快速代入一行数据检查逻辑取一个学生、一条成绩看看能不能对应上。这个方法很土但比干瞪眼靠谱。实际笔试中SQL编辑器一般不会像IDE一样帮你高亮报错所以平时练习就要养成检查的习惯先看JOIN条件有没有少再看GROUP BY和SELECT列是否一致最后看排序和分页。这一套流程走下来能少丢很多分。3. 原理选择题的隐藏陷阱事务隔离级别、索引失效、锁的边界3.1 事务ACID与隔离级别不能只背名字数据库原理题最喜欢在事务隔离级别上做文章。先记住四种隔离级别Read Uncommitted读未提交、Read Committed读已提交、Repeatable Read可重复读、Serializable串行化。它们分别解决了脏读、不可重复读、幻读中的哪些问题我列个表隔离级别脏读不可重复读幻读Read Uncommitted可能可能可能Read Committed不会可能可能Repeatable Read不会不会可能InnoDB通过间隙锁基本解决Serializable不会不会不会注意MySQL默认隔离级别是Repeatable Read但它在InnoDB下通过MVCC和next-key lock把幻读问题基本解决了。笔试问“某隔离级别可以避免哪些问题”直接用上表问“MySQL默认隔离级别是什么”答案是Repeatable Read不是Read Committed。场景题常见形式事务A先查一行数据事务B更新这行后提交事务A再次查询发现数据变了问这是哪种隔离级别下的现象答案是Read Committed及以下说明A在Read Uncommitted或Read Committed隔离级别下。如果A需要两次读取结果一致要升到Repeatable Read。这类题的关键是区分“不可重复读”和“幻读”前者是同一行数据变了后者是查询结果多了一行别搞混。3.2 索引失效的六种典型场景索引这章笔试选择题特别爱考“以下哪个查询会触发索引失效”。我总结了最常见的六种对索引列使用函数或表达式比如WHERE YEAR(create_time) 2024应该改成WHERE create_time 2024-01-01 AND create_time 2025-01-01。隐式类型转换比如手机号字段是varchar查询时写WHERE phone 13800000000数字会被转成字符串索引失效。写成13800000000。LIKE以通配符开头比如WHERE name LIKE %张前缀不确定没法走B树但张%可以走。联合索引不满足最左前缀比如索引(a,b)查询条件只带b。OR连接非索引列比如WHERE a 1 OR c 2如果c没有索引优化器可能全表扫描。改成UNION或让OR两边都有索引。对索引列进行运算比如WHERE salary * 2 10000应该改成WHERE salary 5000。这些场景背后其实是一个理B树按索引列的值有序存储任何对索引列做“加工”的行为都会破坏这个有序性优化器只好放弃索引。写题的时候判断标准就是索引列是否保持了原样。比如WHERE A.id 1 5索引列参与了运算失效改成WHERE A.id 4就正常。还有一个常被忽略的点数据量很小的时候优化器可能觉得全表扫描比走索引更快于是不用索引。这不算“失效”是优化器的正常选择笔试里如果看到这样的选项要能识别。3.3 锁的边界一个场景题看清行锁、表锁、间隙锁锁的题目很容易把人绕晕。我建议把锁想象成门的锁行锁锁住一行表锁锁住整扇门间隙锁锁住一个“空隙”。InnoDB在Repeatable Read隔离级别下默认使用next-key lock也就是行锁加间隙锁来防止幻读。笔试常见场景事务A执行UPDATE user SET status1 WHERE age BETWEEN 10 AND 20age上有索引但没有符合条件的数据。此时事务B执行INSERT INTO user (age) VALUES (15)会被阻塞吗答案会。因为A的查询范围10-20在age索引上产生了一个间隙锁插到15会与该间隙锁冲突。但如果age没有索引InnoDB会对所有记录加锁相当于表锁也会阻塞。这类题要抓三个关键条件是否走索引、是否唯一索引、隔离级别是什么。走唯一索引的等值查询会把记录锁退化为行锁范围查询仍然会有间隙锁不走索引则范围扩大。能判断到这一步选择题基本不会错。还有一个容易混淆的点间隙锁只锁“不存在的记录”不锁已存在的行所以它主要影响的是INSERT操作而不是UPDATE和DELETE。理解了这一点很多场景题就能看穿。4. 数据库设计题ER模型、范式判断、建表语句的完整答题模板4.1 需求分析先把实体和关系找全设计题一般给一段业务描述比如“一个电商平台有用户、商品、订单一个用户可以有多个订单一个订单包含多个商品每个商品在订单里有一个购买数量和成交价”。这时候不要急着写SQL先在草稿纸上把实体和联系标出来。我把这个需求拆一下实体用户、商品、订单、订单明细。联系用户与订单是1:N订单与订单明细是1:N订单明细与商品是N:1。属性用户ID、昵称、手机号商品ID、名称、单价订单ID、用户ID、下单时间、总金额订单明细ID、订单ID、商品ID、数量、成交价。注意总金额是冗余字段可以通过明细聚合算出但为了查询性能通常还是保留。笔试里可以提一句“冗余量小、收益大所以保留”会加分。很多同学设计表时只关心“能不能存下数据”忽略了“业务怎么查数据”。实际上笔试的设计题一般会给出几个查询需求比如“查询某个用户最近10笔订单”你就要确保order表有user_id索引。把这些查询需求列出来再反推索引和字段这样设计出来的表才不是空中楼阁。4.2 范式判断一个反例讲透1NF、2NF、3NF范式题经常给一个表让你判断属于第几范式。先背定义第一范式要求列不可再分第二范式要求非主键列完全依赖主键第三范式要求非主键列之间没有传递依赖。举个例子订单明细表order_detail_id, order_id, product_id, product_name, quantity, price。主键是order_detail_id。product_name依赖product_id而product_id不是主键这里product_name对主键存在传递依赖order_detail_id - product_id - product_name所以不满足3NF。如果主键是(order_id, product_id)联合主键那product_name只依赖product_id是对主键的部分依赖不满足2NF。笔试答题套路是先找候选键再画函数依赖然后套范式定义。只要画出依赖图判断就不难。表拆分时把重复存储的依赖列拆到独立的表里。比如商品信息单独放商品表订单明细只保留product_id这样商品名称修改时只需要更新一处。我在笔试中见过有同学为了“彻底满足3NF”把一个订单表拆成了五张表反而把简单需求搞复杂了。范式是指导不是教条设计的时候要在规范化和查询性能之间做取舍。笔试答案里如果能写出“这里为了查询效率保留冗余字段但通过应用层保证一致性”会显得更老练。4.3 建表语句模板与常见坑设计题的最终落点一般是写建表语句。我给出一个相对标准的写法CREATE TABLE user ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, nickname VARCHAR(50) NOT NULL DEFAULT , phone VARCHAR(20) NOT NULL DEFAULT , create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_phone (phone) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE order ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, user_id BIGINT UNSIGNED NOT NULL, total_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00, status TINYINT NOT NULL DEFAULT 0, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id), CONSTRAINT fk_order_user FOREIGN KEY (user_id) REFERENCES user (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;几个注意点表名和字段名都要有业务含义金额用DECIMAL不用FLOAT外键约束在互联网场景通常不建但笔试里写上外键至少说明你懂。字符集用utf8mb4因为可以存emoji。主键用BIGINT自增而不是随机UUID避免页分裂。这些细节写上去阅卷印象分会不一样。另外判断字段类型时也要注意状态码用TINYINT时间用DATETIME手机号虽然看起来是数字但一般用VARCHAR存因为可能包含前缀或格式变换。这些都是实际开发中会遇到的细节比死记硬背建表语法有用得多。5. 性能优化题从慢查询日志到执行计划一条SQL的体检流程5.1 一条慢SQL的完整排查链路笔试里的优化题通常是给一段生产慢SQL问你怎么排查和优化。我的回答模板分四步第一步确认慢在哪。先看慢查询日志记录实际执行时间和扫描行数如果线上有监控平台直接看SQL的耗时曲线。第二步EXPLAIN看执行计划重点看type、rows、Extra判断是不是全表扫描、有没有文件排序、有没有回表。第三步针对性优化缺索引就建索引写法有问题就改写SQL数据量实在太大就分页、汇总表或走缓存。第四步回归验证对比优化前后的执行计划确认rows降下来、type从ALL变成ref或range。一个常见例子一条订单查询SQL在数据量大后变慢SELECT * FROM order WHERE user_id 123 AND status 1 ORDER BY create_time DESC LIMIT 20;如果只在user_id上建了索引status过滤和排序会额外消耗资源。可以建联合索引(user_id, status, create_time)这样WHERE能用到索引ORDER BY也能利用索引的有序性避免filesort。注意顺序不能乱最左前缀原则决定了查询条件里的等值列user_id、status要放在前面排序字段create_time放在最后。5.2 EXPLAIN执行计划里的关键字段怎么读笔试中如果给你一段EXPLAIN结果重点看这几个字段字段含义关注点type连接类型const/eq_ref/ref/range/index/ALL性能依次变差key实际用到的索引NULL说明没走索引rows预估扫描行数越小越好Extra附加信息Using filesort、Using temporary要警惕Using index是好事把这些字段串起来就能判断SQL的“体检报告”。比如typeALL、rows100000、ExtraUsing where这基本就是全表扫描加条件过滤需要检查WHERE条件有没有索引。如果看到Using filesort说明排序没有利用索引可以考虑让排序列进入联合索引。笔试中你不需要把EXPLAIN每个字段都背下来但上面这几个必须会说因为它们是优化题答题的主要依据。5.3 覆盖索引和回表的取舍优化题里还有一个高频考点覆盖索引。假设表有联合索引(a, b)查询SELECT a, b FROM t WHERE a 1因为要查的列都在索引里不需要回表Extra会显示Using index这是最理想的。如果查询SELECT * FROM t WHERE a 1则只用到了索引的定位能力但SELECT *需要回表取其他字段。此时如果业务只关心a和b两个字段完全可以把SELECT *改成SELECT a, b省掉回表。笔试中优化SQL的第一步就是看能不能用覆盖索引来覆盖查询列。不过别为了覆盖索引把所有字段都塞进索引。索引不是越多越好每多一个索引就多一份写入开销和存储成本要在查询效率和写入性能之间做平衡。这个平衡观是面试官真正想听的别一上来就“加索引”。我见过有些笔试答案写“给所有WHERE字段都加索引”这看起来是优化实际上是破坏。正确的回答节奏是先分析慢在哪再给出最小代价的索引方案最后说明为什么这个方案不会给写入带来太大负担。这样才像一个有经验的工程师而不是背公式的学生。6. 笔试之后的复盘方法把错题变成自己的知识树6.1 错题不要只抄答案要归因到知识点笔试结束后的复盘比刷题更重要。我个人是把错题分三档纯记忆题比如某个隔离级别的定义直接背理解题比如某条SQL为什么慢要画执行计划设计题比如表结构怎么拆要重写一遍并找不同方案对比。每道错题都要归因到一个根节点比如SQL写错是因为JOIN理解不深那就把JOIN的四种类型、执行顺序、与WHERE的搭配全部过一遍而不是只看正确答案。归因到知识点还有一个额外的好处下次遇到同类型的题你能快速识别出题人想考什么。比如看到“两个表都要查出来即使没有匹配也保留”你马上知道是LEFT JOIN/RIGHT JOIN看到“分组后过滤”你马上知道是HAVING。这种识别能力不是靠题海战术堆出来的而是靠每道题后的复盘提炼出来的。笔试的时间很紧如果每道题都要从零开始推理大概率做不完有了知识树很多题你扫一眼就知道考点剩下的只是默写。6.2 构建自己的数据库知识树我建议用小册子或笔记工具维护一棵知识树树根是SQL、索引、事务、锁、设计、优化六个分支。每次笔试遇到的题目就往对应分支上挂写着写着就会发现高频考点越来越集中自己哪里薄弱也一目了然。比如如果你发现所有错题都集中在“索引失效”那下一轮复习就可以专心刷索引专题而不是从头看MySQL。知识树的维护不要追求大而全重点关注“我没想到”的点。比如某次笔试遇到“gap lock”之前完全不知道那就把间隙锁的定义、触发场景、和next-key lock的关系写清楚。这个过程是在把别人的知识变成自己的经验。数据库开发这个岗位的笔试与其说是突击型考试不如说是能力体检。你平时写SQL有没有想清楚为什么设计表结构时有没有考虑过查询场景排查慢查询时有没有形成方法论这张卷子都能反映出来。6.3 一个容易被带偏的复习误区别把Access当主菜最近看到有热搜词是“access数据库开发经典案例解析电子书下载”我多说一句。数据库开发实习生这个方向笔试和面试考的是MySQL/InnoDB这套体系主流互联网公司的业务库也是MySQL和分布式数据库。Access那类桌面型数据库在一些老旧的课程设计里还能见到但跟校招笔试的考察方向基本不搭。复习时重心放在MySQL不要被那种“经典案例解析”的电子书带走节奏。如果你只是想了解关系型数据库的基础概念拿Access练练手也无妨但真正的笔试差的是原理和优化不是看电子书的数量。至少我身边能拿到offer的同学没有一个是通过刷Access电子书上岸的。与其花时间看那些案例不如把时间花在做执行计划分析、写一遍学生选课SQL、手画一张订单ER图上。这些才是笔试真正会考、也真正能提升能力的东西。