
1. 数据库面试的考察范围与常见误区1.1 面试官到底在考察什么最近帮几位准备跳槽的朋友做模拟面试发现数据库板块的翻车率是最高的。数据库常见面试题看着不复杂可一旦被追问到为什么背的结论不成立你在哪一步遇到过这种层面很多人就露馅了。说白了面试官问数据库真正想看的是三件事概念有没有理解透、项目里有没有真正用过、遇到问题有没有一套完整的排查思路。这三件事随便拎一个出来都比能不能默写出B树定义重要得多。先说考察范围。数据库面试题基本可以分成四个梯队第一梯队是SQL基础和表设计覆盖应届生的笔试第二梯队是索引、事务、锁这是1到5年经验后端的高频主场第三梯队是性能调优、慢查询排查到了P6左右几乎必问第四梯队是分库分表、分布式事务、高可用架构这是资深岗位的压轴题。很多候选人栽在第二梯队和第三梯队的衔接上——理论知识背了一堆但没有任何线上案例能佐证。1.2 准备阶段的三个通病我看了不少人的面试复盘普遍存在三个问题。第一个是把MySQL的机制当成通用数据库标准。比如默认隔离级别MySQL是REPEATABLE READ但标准SQL里可重复读是存在幻读的MySQL靠MVCC和间隙锁基本挡住了幻读这个为什么和标准不一样的逻辑如果说不清问题就暴露了。第二个是重原理轻实操。B树的层数算得头头是道但拿到一条真实慢查询不知道怎么用EXPLAIN定位不知道怎么改写SQL。实际上面试官更爱问线上有一条耗时三秒的查询你怎么处理而不是B树层数怎么算。第三个是只背结论不背案例。问你们项目用过什么锁回答我们用了行锁然后没有下文。正确的答法是什么场景、哪条SQL、锁的类型、有没有遇到过阻塞、怎么解决的。哪怕只是一个小案例含量也远超五个死记硬背的结论。下面我按高频题目类别逐个拆解每一类都会给出完整的答题逻辑和面试官真正想听的加分点。2. 索引类问题从B树到最左前缀的完整答题思路2.1 为什么是B树而不是二叉树或B树索引问题几乎场场必问且十有八九从为什么用B树开始。这个问题的核心是磁盘I/O。数据库表数据放在磁盘上一次磁盘I/O大约耗时几毫秒到十几毫秒而内存操作是纳秒级所以树的高度每多一层查询就可能多一次磁盘I/O性能就断崖式下降。B树能压高度靠的是高扇出。InnoDB的默认页大小是16KB假设主键是8字节加上指针大概16字节一行一个页面能存大约1000个键值。两层就能覆盖100万行三层就能覆盖10亿行。也就是说走聚簇索引查一行数据最多三次磁盘I/O这在工程上是完全可以接受的。对比一下其他结构就清楚了结构数据存储1亿数据树高估算范围查询适用场景普通二叉树每个节点一个键约27层差内存场景B树所有层节点都存数据3~5层需要中序遍历早期磁盘索引B树仅叶子节点存数据3~4层叶子链表直接扫关系型数据库索引B树另两个关键设计是内部节点只存键不存数据所以同样的16KB页能容纳更多键树更矮叶子节点用双向链表串起来范围查询比如WHERE id BETWEEN 100 AND 200直接沿着链表扫效率极高。面试时把这个为什么讲透了比单纯画图得分高。提示别忘了补充一句——如果面试官追问为什么不用红黑树红黑树在内存里表现很好但落到磁盘上节点分散、树太高没法按页批量加载因此不适合数据库这种磁盘持久化场景。2.2 聚簇索引与二级索引、回表问到索引就绕不开聚簇索引和二级索引的区别。InnoDB的聚簇索引就是表本身的组织结构叶子节点存的是整行数据二级索引的叶子节点存的是主键值。所以通过二级索引查数据要先在二级索引的B树里定位到主键再回到聚簇索引里把整行数据捞出来这个动作叫回表。为什么要存主键而不是存行地址因为数据库里行数据会因插入、更新、页分裂而移动如果二级索引存的是物理地址主表数据一挪动所有二级索引都要跟着改而存主键值主表位置变了二级索引不用动查询时重新定位一次就可以了。这个设计本质上是牺牲一点查询性能换取维护成本的极大降低。举个例子表里有索引idx_name查询SELECT * FROM user WHERE name 张三过程是先走idx_name找到主键值比如id10再用id10去聚簇索引查回完整行。如果命中的列恰好都在二级索引里比如只查id和name那连回表都省了这就是覆盖索引EXPLAIN里会出现Using index遇到这种情况基本可以放心。2.3 最左前缀原则组合索引的黄金法则组合索引是高频考点最常见的题是索引(a, b, c)哪些查询能用上答案是所有满足最左前缀的查询也就是a、ab、abc如果查询条件是b或bc或ac中间跳了b索引就用不上。CREATE INDEX idx_user ON user(name, age, city); -- 命中只用name WHERE name 张三; -- 命中name age WHERE name 张三 AND age 30; -- 命中name age city WHERE name 张三 AND age 30 AND city 北京; -- 不命中跳过了最左列 WHERE age 30 AND city 北京;为什么最左前缀能帮助排序因为B树的叶子节点本身就是按索引列顺序排列的。所以ORDER BY name, age也能利用这个索引避免文件排序。面试里经常有一个变体题索引(a, b)查询WHERE a 1 ORDER BY b还要不要额外排序答案是不需要因为同一个a值下的b已经有序。这里有个很多人踩过的坑查询条件的书写顺序不影响索引命中优化器会重排条件所以WHERE name 张三 AND age 30和WHERE age 30 AND name 张三效果一样。真正决定命中的是条件里有没有从索引最左列开始形成连续的前缀。2.4 索引失效的常见场景面试里哪些情况索引会失效和最左前缀一样高频。我建议用表格整理答得既快又全场景示例失效原因左模糊匹配LIKE %张三无法确定扫描起点对索引列用函数WHERE YEAR(create_time) 2024索引存的是原始值函数结果需要逐行计算隐式类型转换WHERE phone 123phone是varcharMySQL把值和列都转成数字比较相当于用了函数OR条件含非索引列WHERE name a OR status 1需要并集单索引无法连续扫描索引列参与运算WHERE age 1 30无法直接定位只能全量推导对索引列使用否定条件WHERE name ! 张三优化器认为全扫可能更快注意IN和BETWEEN不一定会失效用不用索引取决于优化器的成本估算和选择率。面试时如果说IN会走全表扫描就太绝对了严谨的说法是要看优化器判断如果区分度足够高就会走索引。提示回答失效场景时最加分的收尾是——我遇到隐式类型转换导致慢查询线上排查时EXPLAIN看到type是ALL后来发现是varchar字段和数字比较加个引号就好了。哪怕只有一句真实经历也立刻把答题从背诵拉升到了实践层面。3. 事务与隔离级别默认隔离级别为什么是送命题3.1 ACID与四个隔离级别事务题目的经典开场是讲一下ACID然后追问隔离级别。ACID四个字母分别对应原子性、一致性、隔离性、持久性。这里的关键不是背名词而是说清楚每个性质由什么机制保证原子性靠undo log事务执行过程中如果出错用undo log回滚到事务开始前的状态。持久性靠redo log先把重做日志写入磁盘崩溃恢复时用redo log重放已提交的事务。隔离性靠锁和MVCC控制多个并发事务之间的可见性。一致性靠应用层业务逻辑保证上述三个特性是它的支撑手段。标准SQL定义了四个隔离级别由低到高分别是读未提交、读已提交、可重复读、可串行化。它们和三类并发问题的关系如下隔离级别脏读不可重复读幻读读未提交 READ UNCOMMITTED可能可能可能读已提交 READ COMMITTED不会可能可能可重复读 REPEATABLE READ不会不会可能可串行化 SERIALIZABLE不会不会不会三个术语的定义要说准确脏读是读了别的事务未提交的数据不可重复读是同一事务内两次查询同一行结果不一样幻读是同一事务内两次范围查询行数不一样。注意区分后面两个不可重复读关注同一行被改幻读关注插入了新行。3.2 MySQL的可重复读为什么挡得住幻读这里就是著名的送命题标准SQL说可重复读是可能发生幻读的但MySQL的默认隔离级别就是REPEATABLE READ而且实际使用中几乎没有幻读问题。原因是MySQL用了两套机制。普通查询是快照读走MVCC。事务里的第一次普通SELECT会生成一个Read View这个快照包含了当前活跃事务ID的列表和一个上限ID。之后每次查询都从这个快照读新插入并提交的行其事务ID大于当前Read View的上限对当前事务不可见所以快照读天然免疫幻读。增删改和SELECT ... FOR UPDATE是当前读走锁。REPEATABLE READ模式下InnoDB会给查询范围加间隙锁和临键锁next-key lock。比如查询WHERE id BETWEEN 10 AND 20除了已有的记录行本身记录之间的空隙也被锁住其他事务无法在这个范围内插入新行从而封死了幻读路径。那MySQL为什么默认用可重复读而不是读已提交这有个历史因素早期MySQL主从复制的二进制日志用的是STATEMENT格式如果主库在READ COMMITTED下执行一条带范围条件的语句由于每次读到的都是最新已提交数据同一语句在主库和备库上可能产生不同结果导致主从数据不一致。可重复读下整个事务内的读是同一快照语句执行结果确定天然适合基于语句的复制。后来MySQL支持了ROW格式的binlogREAD COMMITTED变得安全这也是很多高并发业务选择切换RC的原因。3.3 项目里隔离级别怎么答才不空你们项目用的什么隔离级别这个问题考的不是标准答案而是你有没有做决策的能力。我建议的回答框架是先说明环境——我们用的MySQL InnoDB默认RR再说明业务情况——订单支付属于资金类场景我们确认过没有幻读影响所以保持RR或者——我们线上是RC因为业务并发高、间隙锁容易造成锁等待而且binlog是ROW格式不会有复制不一致问题。如果面试官继续追问RC下不可重复读你们怎么接受你可以说大部分业务做的是订单维度查询每次天然是独立查询对于需要一致读的报表类场景我们用显式事务加锁或直接走可串行化/应用层兜底。这个回答既体现了对机制的理解又展示了取舍思路比背定义高一个段位。提示不要把可串行化说成绝对不用。金融对账、库存扣减等强一致场景确实会用到只是加锁范围大、并发性能低所以一般配合业务改造比如预先锁行来使用。4. 锁机制与死锁面试官想听的回答方式4.1 锁的分类与兼容性锁的题目通常会这样递进先问锁的类型再问死锁怎么产生最后问线上遇到过没有。基础部分要能说清两个维度。按模式分锁分共享锁和排他锁。共享锁之间可以共存排他锁和任何锁都互斥。直观理解多个用户都能同时读一本书共享锁但有人要改这本书时排他锁其他人都得等。按粒度分MySQL有表锁和行锁。InnoDB引入意向锁来解决判断表上有没有行锁这个问题。事务要给行加锁前先给表加意向锁IS或IX这样另一个事务想锁整张表时只要看一眼意向锁就知道表里有没有行被锁不用一行行检查。行锁在InnoDB里又分三种记录锁只锁一行间隙锁锁的是索引记录之间的空隙专门阻止幻读插入临键锁是记录锁加间隙锁的组合锁住一个前开后闭的区间。比如索引里有10、20、30三条记录临键锁可能锁住(10, 20]意味着别人不能在10到20之间插入12也不能插入20。很多人忽略一点这些锁都加在索引上如果SQL没有走索引行锁会退化成表锁这是线上死锁和阻塞的重要根源。4.2 死锁的产生与解除死锁的经典场景是两个事务互相持有对方需要的锁时刻事务A事务Bt1更新 id1 的行持有排他锁更新 id2 的行持有排他锁t2想更新 id2 的行等待B释放想更新 id1 的行等待A释放两个事务互相等待谁也走不下去。解决方案有两个层面数据库层面InnoDB通过等待图检测死锁发现后选择回滚一个代价较小的事务同时抛出死锁错误另外还有innodb_lock_wait_timeout参数做兜底达到超时时间自动放弃。应用层面最好的办法是让所有事务按相同顺序访问资源也就是约定更新多行时先按主键排序从机制上根除死锁。4.3 不同经验层级的答法应届生答到共享锁排他锁、死锁循环等待、MySQL会自动检测回滚就已经达标。中高级开发者要更近一层能区分快照读和当前读解释MVCC和锁是两套并行机制。能说明间隙锁和临键锁什么时候会产生以及为什么用RC可以减少锁冲突。能给出真实案例。比如某订单系统出现过死锁排查后发现是退款和发货两个模块更新同一批订单的方向相反修复方式是在UPDATE语句前统一对订单ID排序死锁彻底消失。面试官其实很知道候选人经验深浅你只要把一个死锁从发现、定位到修复的链条讲完整就是明显的加分项。哪怕只有一个精简案例也比描述十个理论强。提示如果真没遇到过失锁可以用模拟的方式讲我用两个终端分别开事务手动执行同样的UPDATE顺序制造过一次死锁观察SHOW ENGINE INNODB STATUS中的LATEST DETECTED DEADLOCK段理解了等待图。这个回答挺诚实也证明你会用工具。5. 分库分表与高并发场景设计题5.1 什么时候才需要分库分表资深岗位几乎必问分库分表但很多候选人一上来就谈我们分表了面试官追问为什么这个量级就要分表回答就站不住了。这是个设计判断题不是背名词题。定量地说分库分表没有绝对阈值但有几个典型信号单表数据量过了几千万甚至上亿索引树高度开始上涨写入性能和查询性能明显下降单一数据库的连接数成为瓶颈比如连接池最多开几百个连接而应用实例一扩容连接就爆主从复制延时光靠硬件无法缓解binlog解析和回放压力过大。更关键的是分库分表必须是穷尽其他手段之后的方案。我在项目里通常按这个顺序评估先归档冷数据把几年前的订单挪走单表数据量立刻降下来再加缓存层扛读多写少的场景然后做读写分离主库专心写、从库分摊查询最后考虑垂直拆分把大字段、低频字段拆到扩展表里。如果这些都做完了还是顶不住再上水平拆分。面试时按这个思路讲面试官会认为你有真正的架构素养。5.2 分片键怎么选一个选择题暴露架构水平水平拆分最核心的问题是选分片键。选错业务上线后就是灾难级的重构。判断标准有三条基数尽可能大取值分布尽量均匀能覆盖核心业务查询。举个例子订单表如果用order_id做分片键按订单号取模分16库那么按用户查询我的订单这种核心场景就必须把请求广播到16个库再在应用层合并排序性能很差。正确的做法是优先用户视角用user_id分片这样查某用户订单一次路由到固定库。但运营端按商家查订单又麻烦了所以常见的折中是维护一张用户与订单的映射关系表或者用ES做索引先查出订单ID集合再回到各分片捞数据。另外还要注意分片键的均匀性。曾经有项目按月份分表结果双十一那一个月的数据量是平时十倍单月分片压力爆炸。后来改成按用户ID哈希分片加一个月份字段做二级过滤才把热点摊平。5.3 分布式事务与全局ID躲不开的三个连带问题分库分表之后原本单库事务变成了跨库操作三个连带问题面试官一定会问。分布式事务。常见的方案有XA两阶段提交、TCC、本地消息表加MQ。XA虽然强一致但同步阻塞、性能差互联网核心链路很少直接用TCC需要把每个业务操作拆成Try、Confirm、Cancel三个动作开发成本高、对业务侵入大实际用得最多的是本地消息表加上最终一致性比如扣库存和生成订单先写本地消息表再异步发送MQ由消费者完成下游操作失败则重试。面试时如果能说清这三者的取舍和适用场景就已经超过绝大多数候选人。全局ID。分片后数据库自增主键在多库会产生冲突业界常用的方案是雪花算法64位整数包含时间戳、机器ID和序列号趋势递增且不依赖数据库适合作为分布式业务主键。备选方案还有数据库号段模式、Redis自增。要能对比雪花算法高性能但依赖机器时钟时钟回拨会出问题号段模式实现简单但有号段耗尽后的瓶颈Redis自增依赖Redis的可用性。跨分片的分页排序和JOIN。如果分片键是user_id运营端按时间倒序查全量订单就需要从所有分片捞数据后在应用层归并排序结果集越大消耗越高。主流方案是限制深度分页或者把跨分片统计交给ES、大数据组件。如果面试时你确实没有线上分库分表经验不要硬编案例。可以诚实地讲我目前只在单机大表上做过性能优化但对分库分表做过方案调研我的思路是这样的……然后用上面提到的评估顺序加分片键选择逻辑来证明自己的设计能力。面试官反感的是不懂装懂。6. SQL优化与慢查询排查一道题的完整链路6.1 EXPLAIN执行计划怎么读才叫会SQL优化的实操是从读执行计划开始的。面试题通常会给一条慢SQL让你分析为什么慢。第一步永远是EXPLAIN看这几个关键字段type、key、rows、Extra。type字段代表访问类型从好到差排列type含义评价const主键或唯一索引等值查询最好eq_ref唯一索引等值关联很好ref非唯一索引等值查询正常range索引范围扫描可接受index全索引扫描较差ALL全表扫描必须避免key是实际用到的索引rows是估算扫描行数Extra则揭示执行细节。重点看几个Extra标志Using filesort说明排序没法走索引MySQL在内存或磁盘额外排序是大隐患Using temporary说明用了临时表GROUP BY和DISTINCT常见Using index是覆盖索引性能很好Using index condition是索引条件下推MySQL 5.6之后的优化说明部分WHERE条件被下推到存储引擎层过滤。提示EXPLAIN显示的是估算行数不是真实值。线上碰到慢查询如果EXPLAIN看不出问题可以用EXPLAIN ANALYZEMySQL 8新增拿实际执行时间和实际行数有奇效。6.2 慢查询最常见的四类根因结合我处理过的线上问题慢查询的根因九成落在四类第一缺索引或索引顺序不对。条件列没有索引直接全表扫描或者组合索引建反了最左列选错。第二深度分页。LIMIT 1000000, 20意味着MySQL要扫描100万行再丢弃前999980行这是最常见的隐蔽杀手。第三隐式类型转换和函数操作。前面索引失效部分提到的坑在线上慢SQL里反复出现。第四不必要的回表和大字段传输。SELECT *把几千字节的文本字段全捞出来网络和内存都是浪费如果只要订单金额和状态覆盖索引就能解决。另一个经常被忽视的原因是锁等待。SQL本身不慢但事务A持锁长时间不提交事务B的查询一直阻塞。判断方法如果SHOW PROCESSLIST里看到大量Waiting for lock状态就要去查持有锁的事务。6.3 一次完整的慢查询排查案例最后我把一个处理过多轮的案例完整走一遍这个案例可以直接当面试素材用。背景某电商后台订单列表页按用户ID和订单状态筛选按创建时间倒序分页。前期页数小返回正常翻到第1000页后接口超时到30秒。排查第一步就是EXPLAINEXPLAIN SELECT * FROM order_tab WHERE user_id 12345 AND status 1 ORDER BY create_time DESC LIMIT 20000, 20;结果typeALLrows估算60万Extra里有Using filesort。问题很清楚没有覆盖这三列的组合索引导致全表扫描加文件排序。修复方案是建复合索引(user_id, status, create_time)。ALTER TABLE order_tab ADD INDEX idx_user_status_time(user_id, status, create_time);建完索引后第一层优化完成前几十页响应从秒级降到几十毫秒。但翻到很深的页码问题又冒出来了LIMIT 500000, 20还是得扫50万行索引。第二层优化是把分页改写为子查询先取主键再回表回表数量从50万降到20行-- 优化后先走覆盖索引取出主键再回表 SELECT * FROM order_tab WHERE id IN ( SELECT id FROM order_tab WHERE user_id 12345 AND status 1 ORDER BY create_time DESC LIMIT 500000, 20 );实测最深分页从秒级降到百毫秒以内。第三层优化是产品层面把后台列表的深分页限制为只能往前翻500页超过提示使用时间范围查询。后端接口改成游标式分页用上一页最后一条记录的create_time做WHERE create_time ?配合索引天然避免深度扫描这是游戏、订单流水这类场景最常见的方案。这个案例的答题价值不在于SQL写法多精妙而在于完整的排查链路现象、EXPLAIN定位、索引优化、深分页改写、产品层兜底建议每一层都有决策依据。面试官听到这里基本就确认你具备独立的线上排查能力了。数据库面试准备到了最后我个人的体会是别想着把所有面经背一遍把B树、MVCC、锁、分库分表、慢查询这五类核心题的为什么想透再准备一个自己真实的排查案例比什么都管用。面试官追问到第三层时区分度的来源不是你记得多而是你对底层机制的理解和面对问题的分析节奏是否像一个真正干过活的人。