
我最早刷LeetCode的时候压根没想到数据库题会这么有用。当时只是觉得算法题刷累了换个口味做两道SQL题结果一发不可收拾。数据库在LeetCode上是一个独立分类叫做“Database”题目数量虽然没有算法题那么夸张但覆盖的知识面非常广从最简单的单表查询、增删改查到JOIN、子查询、聚合函数、窗口函数再到连续性问题、排名问题、会员活跃度分析这类业务味道很浓的题几乎把一条SQL从业者日常能用到的主干技能都串起来了。不管是准备面试、应付数据库课程设计还是单纯想把MySQL、Oracle这类关系型数据库用得更熟这个题库都值得仔细啃。这篇就把我从一个数据库小白到把这些题刷穿的过程、踩过的坑、总结出来的一套刷题方法论完整写下来。1. 数据库题库到底在考什么整体认识与分段策略1.1 难度分层与真实考点先聊一个很多新手容易误解的事情LeetCode数据库题并不是简单地把算法题换了个马甲。它的难度梯度设计得很有意思。简单题主要集中在单表操作考察的是你对基础SQL语法的掌握程度。比如查询某个条件下有多少条记录、按某个字段排序取前几条、对某个字段去重等等。这些题通常只需要一张表在你理解需求后五分钟内能写出答案算是热手题。但千万不要小看这些简单题它们踩坑的点不少比如NULL值的处理、去重该用DISTINCT还是GROUP BY、字符串比较时隐式类型转换的问题这些细节往往就是面试官喜欢追问的地方。中等题开始出现多表关联、聚合统计、子查询这类进阶操作。你需要同时考虑多张表之间的逻辑关系选择正确的JOIN类型处理分组后的过滤条件还可能要用到EXISTS或IN这类关联子查询。这个阶段是刷题的主力区能覆盖大多数面试和真实业务场景。实际开发中最常写的SQL也基本都落在这个范围里。困难题则是把难度拉满出现频率最高的就是窗口函数和复杂关联。排名问题、连续出现N次的问题、分组内取TopN、累计求和、同比环比、多维度交叉统计这些都是算法题里的动态规划在SQL世界的映射。解决它们需要对窗口函数有非常扎实的理解同时对逻辑拆解能力要求很高。1.2 一条清晰的刷题路线刚开始刷的时候我犯过一个错误东一榔头西一棒子。今天做一道中等题明天又跳去挑战困难题结果就是每道题都写了很久还经常卡住挫败感很强。后来我按照一个固定的顺序重新把题库过了一遍效率一下子高了很多。我的建议是这样分阶段来第一个阶段先把所有简单题刷完目的是熟悉题型和常见的表结构设计。这个阶段不用追求速度但要保证每一道题都能解释清楚为什么这么写尤其是WHERE和HAVING的区别、LEFT JOIN和INNER JOIN的区别这类基础概念一定要在代码之外形成肌肉记忆。第二个阶段刷带JOIN和聚合的中等题。刷的时候不要急着看题解先自己在纸上画出表之间的关系写出需要哪些字段、按什么条件关联、结果要保留哪些行然后再动手写SQL。因为多表关联的思维方式和单表完全不同不是把语法背下来就会的。第三个阶段集中攻克窗口函数相关的困难题。窗口函数这一块建议先花一两个小时专门学一下把ROW_NUMBER()、RANK()、DENSE_RANK()、SUM() OVER()、LAG()、LEAD()这些函数的区别和写法吃透再去刷题就会顺畅很多。另外我也很建议你们关注一下LeetCode的热门100题列表因为它是综合了算法和数据库两个分类里面被做得最多、评论区最活跃的题目质量很稳定。把这份列表里的数据库题挑出来优先刷完性价比非常高因为这些题在面试中出现过的概率也最高。2. 刷题前必须先吃透的几个核心SQL知识点2.1 JOIN到底该怎么理解数据库题里最常出现的就是多张表的关联查询。很多新手一开始搞不明白INNER JOIN和LEFT JOIN的区别其实用一个生活里例子就很好懂。假设你有两张表一张是班级花名册记录了所有同学的名字一张是体检表记录部分同学的视力情况。如果你想知道“哪些同学有体检记录”用INNER JOIN把花名册和体检表按学号关联起来出来的就是两边都有人没有体检记录的同学会被自动丢掉。如果你想知道“每个同学的视力情况没有体检过的同学也保留”那就必须用LEFT JOIN或者RIGHT JOIN以花名册为主表里面的每个同学都会被保留下来体检表里没有的记录就用NULL填充。在实际刷题和做业务开发的时候需要注意一个最经典的坑把LEFT JOIN写成了INNER JOIN的效果。这个问题的根源在于ON条件写得不对。原理是这样的LEFT JOIN会保留左表的所有记录但如果右表有字段在WHERE子句中被过滤了比如WHERE right_table.score 60那么匹配不上的左边记录会因为右边是NULL而同样被过滤掉最终效果和INNER JOIN一模一样。正确做法是把过滤条件放在ON子句里也就是写在关联的同时进行限制。这个细节非常容易被忽视我在后面常见问题的章节会再提一次。2.2 聚合函数与分组统计聚合函数是数据库题里的常客COUNT、SUM、AVG、MAX、MIN这几个必须倒背如流。真正需要仔细体会的是GROUP BY和HAVING的使用时机。GROUP BY的作用是“把同一类别的记录压缩成一行”比如要按部门统计人数就GROUP BY部门编号然后SELECT部门编号, COUNT(*)。这里需要特别注意在标准SQL中SELECT后面出现的非聚合列必须出现在GROUP BY子句里。例如SELECT department_id, employee_name FROM employee GROUP BY department_id这里的employee_name就会报错因为每个分组里有多条员工记录数据库不知道该展示哪一条。MySQL在默认配置下可能会放行这种写法但结果具有随机性实际开发中千万不要依赖这个行为。HAVING和WHERE的区别是另一道送命题。WHERE是在分组之前对每一行进行过滤HAVING是在分组之后对每个组进行过滤。你如果想“找出平均成绩大于80分的班级”这个条件必须用HAVING AVG(score) 80因为平均分只能在分组之后才能算出来。如果用WHERE AVG(score) 80数据库会直接报错或者得不到正确结果因为WHERE在执行的时候分组还不存在。刷题时还有一个常见做法值得记住如果你既要对行进行过滤又要对组进行过滤可以先用WHERE过滤原始行再用HAVING过滤分组结果。比如“找出2020年以后创建的、平均工资高于5000的部门”先WHERE create_time 2020-01-01再GROUP BY之后HAVING AVG(salary) 5000两个条件各司其职逻辑清晰很多。2.3 窗口函数解决困难题的核心武器窗口函数是解锁数据库困难题的钥匙。它的核心思想是让每一行数据在保留自身信息的同事可以看到它所属分组的一些统计信息相当于在一张表的每一行旁边额外挂一列计算结果。最常用的三个排序窗口函数是ROW_NUMBER()、RANK()、DENSE_RANK()它们都跟着一个OVER子句。三者的区别在于并列排名的处理方式ROW_NUMBER()即使遇到并列也强行分出名次比如两个第二名之后直接是第三名RANK()遇到并列会跳过后续名次两个第二名之后跳过了第三名直接是第四名DENSE_RANK()遇到并列不跳号两个第二名之后接着还是第三名。在“分组内取前几名”这类题目里这三个函数的选择直接决定最终结果。我看过一道很典型的题按部门分组取每个部门工资最高的员工。如果用兼容旧版本的写法需要先找到每个部门的最高工资再回到原表去JOIN如果用了窗口函数直接SELECT department_id, employee_name, salary, ROW_NUMBER() OVER(PARTITION BY department_id ORDER BY salary DESC) AS rn然后在外层把rn 1的记录筛出来一条SQL就搞定逻辑简单很多。另外SUM()、AVG()等聚合函数也可以做窗口函数来用这时它们的区别就很明显了普通聚合会把多行压缩成一行窗口聚合则不会减少行数而是把聚合结果复制到每行上。这个特性在算累计求和、移动平均、同环比的时候特别有用。2.4 子查询不要怕嵌套子查询的本质就是“在一个查询的结果上再继续做查询”类似剥洋葱。它分为相关子查询和不相关子查询两种不相关子查询先独立执行比如查出平均工资再把这个结果当作外层查询的条件相关子查询则要在外层每走一行时都要执行一次比如“找出比本部门平均工资高的员工”每次都需要根据当前行的部门重新计算平均工资。刷题的时候我建议不要一上来就写特别复杂的嵌套而是先写一个中间查询把部分结果作为临时表在外层再用JOIN或者WHERE条件把它们拼在一起。这种写法虽然看起来多了一层但思路清晰调试也方便。很多三年经验的开发者在实际工作中也是这么干的先把复杂的逻辑拆成子查询确认识结果正确之后再优化合并。2.5 工具选择在哪写SQL题LeetCode官网的数据库编辑器默认支持MySQL也需要选择版本比如MySQL 8.0。如果你用了窗口函数就必须要选8.0以上版本因为窗口函数是8.0才加的。我自己在本地也装了MySQL用Navicat作为日常调试工具遇到重点题我会把题目里的表结构和样例数据导入本地库断点式地一步步观察中间结果。这种本地复现方式虽然比网页编辑器多几个步骤但对理解题目的帮助非常大因为你可以在每一步执行完SELECT * FROM (中间结果)看一下数据长什么样网页编辑器里做这个操作很别扭。还有一些人会问要不要装Oracle或者其他数据库来练我的看法是没必要。LeetCode的判题逻辑和主流数据库语法基本兼容而你练SQL最重要的是练思路不是练某个数据库的私货特性。把MySQL玩熟再迁移到其他数据库只是语法差异的问题本质上不会特别痛苦。3. 典型真题实操从简单到困难的完整拆解3.1 简单题员工奖金问题这是LeetCode第577题核心是要取出所有员工及其对应的奖金没有奖金的员工也要显示且奖金为NULL。表结构大概是这样的CREATE TABLE Employee ( empId INT PRIMARY KEY, name VARCHAR(255), supervisor INT, salary INT ); CREATE TABLE Bonus ( empId INT PRIMARY KEY, bonus INT );需求描述返回每个员工的姓名和奖金数额如果一个员工没有奖金则奖金显示为NULL。能直接把这道题做对基本上就理解了LEFT JOIN的本质。我给出的解法SELECT e.name, b.bonus FROM Employee e LEFT JOIN Bonus b ON e.empId b.empId WHERE b.bonus 1000 OR b.bonus IS NULL;注意这里因为要求“没有奖金的员工也要显示”必须使用LEFT JOIN以员工为主表。而过滤条件必须带上b.bonus IS NULL很多人会漏掉这个条件结果把没有奖金的员工全部过滤掉了。另外过滤条件要写成b.bonus 1000 OR b.bonus IS NULL不能只写b.bonus 1000因为NULL参与比较时结果是UNKNOWN会被过滤掉。这道题还有一个衍生问法查询奖金小于1000的员工但空奖金不算小于1000。那就把OR b.bonus IS NULL去掉即可。同一个题目换个条件SQL写法完全不同这个敏感性要培养起来。3.2 中等题第二高薪水这是第176题一道非常经典的入门中等题。表Employee包含id和salary两个字段需要找出第二高的薪水如果没有第二高薪水则返回NULL。表结构和数据示例CREATE TABLE Employee ( id INT PRIMARY KEY, salary INT ); INSERT INTO Employee VALUES (1, 100), (2, 200), (3, 300);第二高薪水的意思是不包括最高薪水的最大值。一个常见的思路是先按薪水排序跳过第一名取第二行。对应的SQLSELECT IFNULL( (SELECT DISTINCT salary FROM Employee ORDER BY salary DESC LIMIT 1 OFFSET 1), NULL ) AS SecondHighestSalary;这里的IFNULL作用是当子查询查不到任何记录时比如表中只有一行数据返回NULL而不是报错。ORDER BY salary DESC把最大的薪水排在最前LIMIT 1 OFFSET 1表示从第二条开始取一条。DISTINCT是为了去除重复薪水否则如果最高薪水和第二高薪水相同结果就不对了。这个题还有一种写法是自连接或者相关子查询比如计算比当前薪水大的数据有多少个数量为1就是第二高。但LIMIT OFFSET的写法在面试中更直接也更符合大多数人的阅读习惯。面试官如果想考察窗口函数会把题改成“找出每个部门的第二高薪水”这时候就要用DENSE_RANK或者ROW_NUMBER配合PARTITION BY部门分组。3.3 中等题部门工资最高的员工这是第184题也是网络热词里经常出现的“部门最高薪水”问题。表Employee包含id、name、salary、departmentId表Department包含id和name。需求是找出每个部门工资最高的员工。注意这里可能出现并列最高所以不能用简单的MAX。表结构CREATE TABLE Employee ( id INT PRIMARY KEY, name VARCHAR(255), salary INT, departmentId INT ); CREATE TABLE Department ( id INT PRIMARY KEY, name VARCHAR(255) );第一种解法使用窗口函数。按部门分组对工资进行排名取排名为1的所有记录SELECT d.name AS Department, e.name AS Employee, e.salary AS Salary FROM ( SELECT id, name, salary, departmentId, DENSE_RANK() OVER(PARTITION BY departmentId ORDER BY salary DESC) AS rk FROM Employee ) e JOIN Department d ON e.departmentId d.id WHERE e.rk 1;这里用DENSE_RANK而不是ROW_NUMBER是因为并列最高工资时两个人都应该输出。如果用ROW_NUMBER并列的人只保留一个就会漏数据。这又是一个需要仔细辨别函数用法的典型场景。第二种解法用GROUP BY和JOIN。思路是先找出每个部门的最高工资再把这个结果作为临时表跟原始表关联找出对应的人SELECT d.name AS Department, e.name AS Employee, e.salary AS Salary FROM Employee e JOIN Department d ON e.departmentId d.id JOIN ( SELECT departmentId, MAX(salary) AS max_salary FROM Employee GROUP BY departmentId ) t ON e.departmentId t.departmentId AND e.salary t.max_salary;第二种方法虽然比窗口函数多了一次子查询但避免使用窗口函数在MySQL 5.7及以下版本也能跑也是面试中经常被期待的输出方式。3.4 困难题连续出现的数字这是第180题。表Logs只有一列num需要找出所有至少连续出现三次的数字。表结构CREATE TABLE Logs ( id INT PRIMARY KEY AUTO_INCREMENT, num INT );这里“连续”指的是id连续且num相同。最直接的思路是多表自连接把Logs表自身关联三次条件是id分别为当前行、下一行、下下行同时num都相等SELECT DISTINCT l1.num AS ConsecutiveNums FROM Logs l1 JOIN Logs l2 ON l1.id l2.id - 1 AND l1.num l2.num JOIN Logs l3 ON l1.id l3.id - 2 AND l1.num l3.num;这个解法的核心是理解自连接同一张表假设成三个不同的别名通过id之间的偏移关系模拟“相邻的三行”。如果要求连续出现N次就自连接N次条件改成id相差N-1。这种思路非常符合业务里的“连续签到N天”问题。窗口函数的解法也值得掌握。连续出现的本质是num相同而id递增那么可以在分组内部安排一个行号然后看num的值和行号之差如果差值相同说明这几行是连续的SELECT DISTINCT num AS ConsecutiveNums FROM ( SELECT id, num, id - ROW_NUMBER() OVER(PARTITION BY num ORDER BY id) AS diff FROM Logs ) t GROUP BY num, diff HAVING COUNT(*) 3;这里的技巧比较巧妙ROWNUMBER在相同的num内按id排序生成连续的序号如果id本身就是连续的则id减去行号是一个固定值一旦出现断档差值就会变化。然后按num和diff分组数量达到3说明存在连续三次的数据。这道题充分体现了窗口函数在处理“连续性”问题上的威力面试中如果能在白纸上推导出这个解法加分效果非常明显。3.5 困难题游戏玩法分析这道题是LeetCode第534题或第550题的不同变体核心是玩家在一个游戏中的首次登录时间和后续活动分析。很多数据库课程设计里也有类似需求比如用户首次访问网站的时间、每个用户最后一次下单日期、连续活跃天数等。表结构CREATE TABLE Activity ( player_id INT, device_id INT, event_date DATE, games_played INT, PRIMARY KEY (player_id, event_date) );比如需求是找出每个玩家的首次登录日期。最简单的写法是SELECT player_id, MIN(event_date) AS first_login FROM Activity GROUP BY player_id;这个看起来很简单但真正有难度的是把首次登录日期作为过滤条件再去查询首次登录后一天是否也登录了这就涉及到把MIN(event_date)的结果当作子查询跟原表做关联SELECT a.player_id, a.event_date AS login_date, a.games_played FROM Activity a JOIN ( SELECT player_id, MIN(event_date) AS first_day FROM Activity GROUP BY player_id ) t ON a.player_id t.player_id AND a.event_date DATE_ADD(t.first_day, INTERVAL 1 DAY);这类进阶写法完美展示了聚合查询和子查询的组合。先算出每个玩家首次登录日期再通过日期运算判断次日仍在活跃这种模式在真实的用户留存分析场景里非常常见做运营数据报表时经常要用到。在这道题上我还遇到过一种情况玩家在同一天有多次活动记录。如果主键定义是(player_id, event_date)同一天只有一条记录问题不大如果允许多条那么需要先对player_id和event_date去重再用去重后的结果作为基础数据继续查询否则统计出来的活跃人数会虚高。这个预处理细节很值得写在笔记里面试时主动提出来能体现你的细心程度。4. 从刷题延伸到面试和课程设计4.1 刷题之外的面试必问索引、事务、死锁LeetCode数据库题能锻炼你的SQL编写能力但数据库面试还经常涉及另一堆概念性内容比如索引、事务、隔离级别、死锁。这些知识在在线判题系统里很难直接体现但面试官几乎必问。索引那部分最核心的问题是为什么用B树而不是哈希表或者链表。B树是矮胖型多路搜索树数据都存在叶子节点叶子节点之间用指针连成有序链表所以既支持快速单行查询也支持范围查询和排序。哈希索引虽然单行查询更快但遇到范围查询、排序、模糊匹配就会退化。这个知识点建议配合EXPLAIN在本地库多观察几次眼见为实。死锁则是另一个高频问题。死锁的本质是两个或多个事务各自持有一些资源同时又在等待对方释放资源形成一个循环等待。经典案例是事务A更新表1后去更新表2事务B更新表2后去更新表1两边谁也不让谁最后数据库检测到死锁会强制回滚其中一个事务。解决死锁的常规策略是保证事务以固定的顺序访问资源以及合理控制事务的持锁时间。事务的隔离级别也要重点理解读未提交、读已提交、可重复读、串行化这四级每一级解决什么问题、带来什么新问题都需要能用自己的话讲清楚。MySQL默认是可重复读Oracle默认是读已提交这个考点经常出现。4.2 刷题之外的性能优化思维LeetCode判题只关心结果正确但真实业务里SQL性能同样重要。面试官很喜欢让候选人分析一条慢SQL的优化方案所以平时刷题时也要渐渐养成姿势好习惯。尽量不用SELECT *只查询需要的字段不仅能减少网络传输量还能让索引覆盖查询变得更可能。多表关联时尽量让小表驱动大表在MySQL里可以通过STRAIGHT_JOIN或者调整子查询顺序来影响优化器的执行计划。WHERE条件里避免对字段做函数运算例如WHERE YEAR(create_time) 2024这个写法会导致索引失效正确写法是WHERE create_time 2024-01-01 AND create_time 2025-01-01。还有一个容易被忽略的重点连表查询时连接列的数据类型要保持一致。如果一边是INT一边是VARCHARMySQL会做隐式类型转换可能导致索引失效全表扫描。我在一道LeetCode题里就碰到过类似情况本地复现的时候把关联字段从VARCHAR改成INT后执行时间从毫秒级降到了微秒级。4.3 数据库课程设计怎么利用这些题很多学生朋友在课程设计阶段拿到的是类似学生选课系统、图书管理系统、电商订单系统这类题目这些系统里涉及的SQL操作很大概率都能在LeetCode数据库题里找到原型。学生选课系统要写“每个学生的平均成绩”“每门课的最高分和选课人数”“没有选课的学生名单”这些对应的是聚合、JOIN、NOT EXISTS。图书管理系统要查“借阅次数最多的书籍TOP10”“超过三个月未还书的读者”这些对应的是排序、日期函数、窗口函数。把这些SQL写熟练课程设计里的核心功能基本就搞定一大半了。另外在网上有很多现成的练习用数据库比如北风数据库、经典的学生-课程-成绩三表结构这些库的数据量很适合做SQL练习。用Navicat导入后自己给自己出题模拟业务场景比单纯刷题更接近实战也更能锻炼业务理解能力。遇到“数据库课程设计”不知道选什么题目的朋友我很推荐拿一道中等难度的LeetCode题稍微改一下表结构扩成一个小系统比如把“部门工资最高的员工”扩展成“各部门薪资排行报表管理系统”既有难度亮点又能覆盖很多知识点。4.4 关于周赛和刷题节奏的个人观察LeetCode每周都有周赛里面算法题是主力偶尔也会穿插数据库题。热词里出现的“leetcode周赛430”就是一个典型例子。如果你的重心目标是数据库我不太建议每周都死磕周赛里那种限时高难度的算法题因为这和数据库SQL题的核心考法不一样。数据库题更看重准确性和严谨性算法题更看重速度。但每周的周赛题目中一旦出现Database题其实是很值得去做的因为它们通常代表了题目设计的一个新方向做完以后把题解汇总起来能形成很好的素材库。刷题节奏上我个人的体会是细水长流比突击有效得多。每天固定两小时刷一两道数据库题坚持三个月比周末刷十个小时更容易形成长时记忆。LeetCode有每日一题和打卡机制建议用起来而且把打卡界面当作自己的成就墙看到连击天数以后会有一种舍不得断签的心理反而成了坚持的动力。5. 常见错误与排查技巧实录5.1 高频错误速查表以下这些错误是我自己在刷题和带新人时遇到最多的情况整理成一张表方便你对照自查错误类型错误写法正确写法原因分析NULL值被过滤WHERE salary 1000WHERE salary 1000 OR salary IS NULLNULL参与比较时结果为UNKNOWNLEFT JOIN变成INNER JOINWHERE right_table.score 60把条件放ON子句WHERE中的NULL过滤掉左表保留行GROUP BY字段遗漏SELECT dept_id, name, AVG(salary) GROUP BY dept_idSELECT dept_id, AVG(salary) GROUP BY dept_id非聚合列必须出现在GROUP BY排名函数选错并列第一时用ROW_NUMBER并列第一时改DENSE_RANKROW_NUMBER不给并列留位置窗口函数版本不支持在MySQL 5.7用ROW_NUMBER换MySQL 8.0或改子查询窗口函数是8.0新特性ORDER BY / LIMIT顺序LIMIT 1 OFFSET 0 ORDER BY salary DESCORDER BY salary DESC LIMIT 1 OFFSET 0ORDER BY必须先排序再取数去重方式错误用SELECT num用SELECT DISTINCT num多行重复会输出重复结果日期函数不会用DATE_ADD不会手动字符串拼接DATE_ADD(event_date, INTERVAL 1 DAY)日期运算要使用内置函数5.2 调试SQL的土办法在线判题系统报错以后最烦的事情是不知道数据到底长什么样。我的土办法是先在本地建一个一模一样的表结构和样例数据把SQL的各个部分拆开跑一层层看中间结果。举一个真实案例。我刷一道“每个产品在2020和2021年都销售”的题时先写了子查询统计每年的销售情况结果发现2020年有记录的产品在2021年也有记录但最终输出却少了一部分。我用本地库逐步排查先跑最内层的子查询看每年销售结果发现因为表中销售日期是DATETIME类型我直接用YEAR(sale_date) 2020但数据里只有一条记录是2020-01-01 00:00:00其他都是NULL。问题根源在于创建表时日期字段有大量NULLYEAR(NULL)返回NULL判断自然是NULL。这个坑在题目描述里几乎不会明说只有去数据库里查一遍才能发现。所以调试SQL的时候我的习惯是三步走第一步先跑最内层确认识子查询结果符合预期第二步把子查询结果作为临时表跟外层表JOIN逐层往上检查第三步最后加最终条件比如HAVING、ORDER BY、LIMIT。每加一个条件就重新看一遍结果不要等整条SQL写完再一起调。5.3 三道错题复盘最后复盘三道我印象最深的错题都是简单的错误但后果很严重。第一道是“第二高薪水”我第一次写的时候忘了DISTINCT结果在数据里有多个相同工资时输出错误。复盘的时候意识到题目里的“第二高”不是物理上的第二条记录而是逻辑上的第二个不同的工资值所以去重是必须的。第二道是“删除重复邮箱”这道题要求保留每个邮箱的最小id记录。我一开始用GROUP BY email取MIN(id)然后想把非最小id的记录删掉但DELETE和子查询在同一张表上的处理方式很考验写法和数据库版本。在MySQL里不能直接在同一个表的DELETE语句中同时SELECT同一张表需要先通过派生表包一层。这个限制如果我当时不知道就会一直在报错里打转。第三道是“大洲和国家”这类带多表关联的题我忘了考虑关联字段的索引。本地数据量小所以看不出问题但面试官会追问“如果你的表有十万行怎么办”这时候如果没有给关联字段建索引连接查询会慢得难以接受。所以后来我刷题的时候都会顺手用EXPLAIN看一下执行计划看看有没有全表扫描这也是区分普通工程师和资深工程师的一个细节。6. 一点后续可以扩展的方向数据库题库刷完之后自然延伸的方向其实很多。后来我自己经常碰到“数据库同步”“向量数据库”这类话题前者是因为公司要做主从同步和数据迁移后者是AI相关的项目需要做相似度检索。这两个方向都会用到大量SQL知识但又各有各的新体系。如果你对“数据库同步软件”感兴趣可以从理解binlog和主从复制的原理开始。MySQL的主从复制本质上是把主库的二进制日志同步到从库并重放中间涉及同步延迟、断点续传、主键冲突等问题。这个方向不适合在LeetCode上刷适合搭建两个本地MySQL实例做一次主从配置亲手观察同步过程。向量数据库则更偏向于处理非结构化数据核心是向量索引和相似度计算语法和传统关系型数据库差异很大。不过向量数据库的底层很多也借鉴了数据管理的思想比如分片、副本、索引结构所以传统数据库基础越扎实学起来就越轻松。我个人觉得把LeetCode数据库题刷完并不是终点它更像是一张通行证让你有了足够的SQL能力去面对真实世界的各种数据问题。但核心还是要慢下来写每一行SQL去思考数据库为什么要这样设计去体会数据之间的关系。我知道数据库这份合集对很多人来说没有算法题那么刺激但它却最接近真实业务里每天要面对的活。你能把这些题做到看一眼就能写出干净SQL到哪儿都饿不着这句话放在当下依然不过时。