后端开发面试深挖 15 题
每题都拆成「平淡回答 → 追问逻辑 → 高分示范」三层,看清后端高频题到底在考什么。
题目为高频真实问法;①②③ 三层答案为模拟示范,非真实面经
① 简历常见平淡回答
「建立联合索引 a,b,c,查询条件必须从 a 开始按顺序写,跳过 a 索引就失效了,用 EXPLAIN 能看出来。」
为什么不够:只背了规则表象,没说清复合索引在 B+ 树物理排序上的级联本质(首列相同才排次列),也没解释跳列或范围查询时 EXPLAIN key_len 到底如何体现有效长度,缺乏工程排查与重构思路。
② 面试官追问逻辑
③ 深挖后可量化的高分示范
复合索引在 B+ 树上是严格按字段顺序级联排序的,只有前一列值相等时才会比较下一列;跳过首列直接查次列就像查字典跳过首字母,无法二分。排查失效看 EXPLAIN 的 key_len:三列联合索引 `(a,b,c)` 查 `a=1 and c=2` 时,key_len 只体现 a 的字节数,说明 c 只作为过滤条件并未走索引定位。工程中若该跳列查询高频,优先评估新建联合索引 `(a,c)` 或调整列顺序;若写操作频繁需防范索引膨胀,则依赖 MySQL 5.6+ 的索引下推(ICP)机制过滤,尽量减少回表开销。
① 简历常见平淡回答
「一般先改数据库再删缓存,也可以先删缓存再改数据库,或者用延时双删。」
为什么不够:像背诵方案清单。没有解释两种顺序在并发交错下产生脏数据的具体时序,也没给出生产环境下针对网络延迟和删除失败的兜底容灾策略。
② 面试官追问逻辑
③ 深挖后可量化的高分示范
我在生产中首选「先更数据库,再删缓存」。如果先删缓存,并发读请求在数据库事务尚未提交前就会命中 miss,把 DB 旧值重新回填到缓存形成长期脏数据;而先更 DB 再删缓存的脏数据窗口仅存在于写事务提交与删缓存之间,概率极低。针对最后一步删缓存可能因网络抖动失败的问题,我们不把删除放在同步业务代码里,而是通过 Canal 监听 MySQL Binlog 异步投递 MQ 进行重试删除,让业务逻辑解耦。只要容忍百毫秒级的最终一致性,就能把双写风险降到最低。
① 简历常见平淡回答
「前端提交后把按钮变灰,后端在数据库加唯一索引,或者用 Redis 存一个 token。」
为什么不够:只把责任推给前端或者数据库。前端禁按钮防不住弱网重试和黑产脚本,直接依赖 DB 唯一键会频繁抛出异常消耗数据库连接,没有串联起完整的防御链路。
② 面试官追问逻辑
③ 深挖后可量化的高分示范
我们按「前端链路阻断 → Redis 分布式防重 → 数据库唯一约束兜底」三层防护。客户端发起请求时生成唯一的 requestId,后端在拦截器用 Redis 执行 `SET order:req:{requestId} 1 NX PX 3000` 抢占分布式锁:抢锁失败直接返回“请勿重复提交”;抢锁成功才进入真实业务事务。过期时间一般设为接口超时时间(如 3 秒),既能覆盖弱网重放,又防止业务异常崩溃后永久锁死后续提交。最后在订单表对 `(user_id, client_req_id)` 建唯一联合索引,作为落库的绝对硬兜底,阻断一切极端并发漏单。
① 简历常见平淡回答
「在 SQL 里加 where 库存大于 0,或者用 Redis decr 扣减,就不会超卖了。」
为什么不够:混淆了不同并发量级下的架构取舍。SQL 条件更新在高并发下会引发严重的单行排他锁争抢,而只提 Redis decr 无法保证扣减与校验余量的原子性,也没提缓存与落库的对账。
② 面试官追问逻辑
③ 深挖后可量化的高分示范
普通低并发场景,我们直接用带有行锁防御的 SQL 条件更新:`UPDATE item_stock SET remain = remain - ? WHERE item_id = ? AND remain >= ?`,利用行级排他锁杜绝超卖。但当大促峰值 QPS 破万时,行锁竞争会导致大量数据库连接池等待甚至打崩实例。我们升级为「Redis + Lua 脚本预扣减 + MQ 异步落库」:在 Lua 脚本内原子完成库存判断与扣减,抗住高并发流量;随后发送事务消息异步落库建单。我们秒杀单品承载能力从原先 MySQL 的 700 QPS 稳定提升至 1.2 万 QPS,并通过延时对账任务兜底补偿超时未支付的释放库存。
① 简历常见平淡回答
「主要看 type 是不是 ALL,看用没用上 key,还有 rows 是不是很大。」
为什么不够:停留在零散看字段。面试官想听的是一条成体系的诊断流:从扫描方式(type)的级别差异,到实际匹配字节数(key_len),再到 Extra 中的严重性能预警(Using filesort/temporary)。
② 面试官追问逻辑
③ 深挖后可量化的高分示范
我拿到慢 SQL 会按「type 访问类型 → key 与 key_len → Extra 辅助状态」三步诊断。首先看 type:从最优的 const/eq_ref 到 ref,如果降级为 range 甚至 index/ALL,说明索引效率极差;接着比对 key 与 key_len,验证多列联合索引到底生效了前几列。重点警惕 Extra 中的 Using filesort(未利用索引有序性产生内存或磁盘排序)和 Using temporary(创建临时表)。定位后通常通过建立联合索引覆盖查询列、将排序字段融入索引尾部,或改写深度分页来消除文件排序与回表,直击 I/O 瓶颈。
① 简历常见平淡回答
「因为 Executors 创建的线程池可能导致内存溢出 OOM,必须自己手写 ThreadPoolExecutor。」
为什么不够:只记住了 OOM 这一句口诀,没解释具体是哪个工厂方法的哪项默认参数存在隐患,也没讲清生产环境下如何科学评估核心线程数与阻塞队列容量。
② 面试官追问逻辑
③ 深挖后可量化的高分示范
Executors 的核心弊端在于隐藏了无界资源的风险:`newFixedThreadPool` 默认采用无界的 `LinkedBlockingQueue`(容量是 Integer.MAX_VALUE),请求堆积时会直接撑爆堆内存导致 OOM;而 `newCachedThreadPool` 最大线程数设为 Integer.MAX_VALUE,瞬间高并发会无休止创建线程耗尽系统资源。生产环境必须显式用 `ThreadPoolExecutor`:指定有界队列(如 ArrayBlockingQueue 并限制几百到几千容量);根据业务性质区分 CPU 密集型(N+1)或 I/O 密集型(2N 甚至根据等待时间比率放大);并选用 CallerRunsPolicy 做背压反弹或记录持久化日志兜底。
① 简历常见平淡回答
「先看下服务器 CPU 和内存高不高,然后查慢 SQL 日志,看是不是数据库慢了。」
为什么不够:排查动作零碎盲目,缺乏系统化的排查漏斗。面试官想看你从宏观指标、分布式链路追踪(APM)、宿主机资源到 JVM 运行时层层下钻的完整工程素养。
② 面试官追问逻辑
③ 深挖后可量化的高分示范
我按「宏观观测 → 链路定位 → 单机画像」三层漏斗排查。首先看监控大盘与 APM(如 SkyWalking):比对 P99 耗时、QPS 变化及报错率,迅速圈定是全量变慢还是特定接口,通过 TraceID 识别耗时具体卡在下游 RPC、Redis 还是数据库。如果是应用层卡顿,登录机器看 CPU 负载与连接数;若 CPU 正常但响应极慢,通常是线程池耗尽或下游 I/O 阻塞,用 `jstack` 抓线程快照排查是否大面积处于 WAITING 或 BLOCKED。曾有核心接口耗时从 80ms 飙至 4 秒,通过链路追踪抓出是调用第三方物流超时且未设合理 timeout,耗尽了应用连接池。
① 简历常见平淡回答
「把过期时间设长一点,或者用 Redisson 的看门狗机制自动给锁续期。」
为什么不够:只知其然不知其所以然。没讲清锁提前释放后导致的“并发重复执行”与“误删他人锁”的双重灾难,也没说清看门狗续期的底层定时任务机制与容灾边界。
② 面试官追问逻辑
③ 深挖后可量化的高分示范
锁提前释放会导致两大灾难:并发线程同时进入临界区破坏幂等,以及当前线程业务结束后误删他人新加的锁。因此释放锁必须用 Lua 脚本校验客户端唯一标识(UUID)确保原子删除。解决提前释放主流采用 Redisson 的看门狗(Watchdog)机制:客户端加锁成功后,后台启动一个 Netty 时间轮定时任务,每隔 10 秒(lockWatchdogTimeout 的三分之一)向 Redis 发送续期脚本延长 TTL。一旦持锁节点宕机,心跳任务中断,锁在 30 秒后自动自然过期,既防止死锁又保障长任务平稳执行。
① 简历常见平淡回答
「调第三方接口加个 try-catch,超时了就重试几次,或者用 Sentinel 做熔断。」
为什么不够:重试往往会加剧第三方雪崩并耗尽自身线程池。面试官想看从超时熔断隔离、异步解耦到业务兜底降级的完整高可用闭环。
② 面试官追问逻辑
③ 深挖后可量化的高分示范
第三方接口属于不可控的外部依赖,防御核心是「快失败、强隔离、异步解耦与降级兜底」。首先必须配置强硬的超时阈值(如 connect 1 秒、read 2 秒),严禁盲目全量重试;其次引入熔断器(如 Sentinel 或 Resilience4j),采用独立线程池隔离第三方调用,防止外部挂起占满本服务 Web 容器线程。当错误率超过 40% 时直接触发熔断降级,返回兜底文案或落本地队列异步重试。我们曾遭遇第三方电子签章服务频发 10 秒超时,上线隔离与熔断后,将核心审批链路的级联故障率阻断为零,P99 耗时稳定在 120ms。
① 简历常见平淡回答
「数据量太大了就分库分表,按用户 ID 取模分 16 张表,旧数据定时删掉。」
为什么不够:一上来就分库分表是典型杀鸡用牛刀。面试官看重的是成本阶梯意识:冷热分离归档 → 清理非必要索引 → 真正分片时的分片键选择与跨维度查询解决方案。
② 面试官追问逻辑
③ 深挖后可量化的高分示范
面对 2000 万单表,我们遵循「先冷热归档,确有瓶颈才物理分表」的演进策略。多数业务 95% 查询集中在近 3 个月,我们开发定时离线任务,利用主键范围按批次(每批 2000 条加休眠防从库延迟)将半年前的完结订单归档至 ClickHouse 或 MySQL 历史表,原表数据做逻辑删除与碎片整理,单表维持在 500 万行健康水位,B+ 树维持 3 层索引。若业务持续暴增必须分表,采用 ShardingSphere 按 `user_id` 水平拆 32 张表,针对商户端跨分片查询,通过 Canal 将 Binlog 同步至 Elasticsearch 构建宽表进行多维检索。
① 简历常见平淡回答
「保证不丢要在发消息时等确认,消费完再发 ACK;防重复消费在数据库建个唯一索引。」
为什么不够:答题只有结论没有闭环。不丢消息需要涵盖「生产端重试、Broker 刷盘副本同步、消费端手动 ACK」全链路;防重消费需要讲清幂等设计与去重表的协同机制。
② 面试官追问逻辑
③ 深挖后可量化的高分示范
防丢必须打通生产、存储、消费三端闭环:生产端开启发布确认机制(如 RabbitMQ Publisher Confirm),收到 NACK 或网络超时即时重试;Broker 端开启持久化并将刷盘模式配置为同步刷盘、多副本同步复制,杜绝主节点宕机丢消息;消费端必须关闭自动 ACK,务必在本地业务事务提交成功后,再显式向 Broker 发送回执。防重复消费则依靠消费端的业务幂等设计:利用分布式全局 messageId,在数据库事务内优先插入消费流水去重表,利用唯一主键拦截网络重试引发的二次消费,保证无论投递多少次状态严格一致。
① 简历常见平淡回答
「之前后台订单列表很慢,我查了慢查询日志,给表加了几个索引,后来就变快了。」
为什么不够:缺乏具体的工程诊断现场。优秀候选人能讲清指标恶化的业务后果、用 EXPLAIN 揪出的深层病因(如单列索引选错、隐式转换或 filesort),以及量化前后的对比。
② 面试官追问逻辑
③ 深挖后可量化的高分示范
之前商户后台导出日订单经常 504 超时。排查慢日志发现查询条件含 `merchant_id`、`created_at` 和 `status`,单表有 1800 万数据。EXPLAIN 分析显示优化器选了 `created_at` 单列索引,扫描行数预估高达 85 万行,且 Extra 出现了致命的 `Using filesort` 导致单次查询耗时 9.2 秒。我们补建了联合索引 `(merchant_id, created_at, status)`,精准匹配左前缀并让排序直接利用索引树物理顺序。优化后扫描行数从 85 万锐降至 260 行,消除 filesort,P99 耗时降至 110ms,且写放大耗时增加不到 2%,彻底解决了超时问题。
① 简历常见平淡回答
「我会加班加点改代码,改到达标为止,如果实在不行就跟领导申请延期发版。」
为什么不够:缺乏工程应急思维与商业全局观。临近上线盲目大改代码极易引入未知致命 Bug;直接延期伤害业务承诺。面试官想看的是风险控制、应急降级与分期演进的成熟权衡。
② 面试官追问逻辑
③ 深挖后可量化的高分示范
上线前夕原则是「严控改动爆炸半径,保业务稳定上线,杜绝临阵大重构」。我们曾在大促前 24 小时压测发现商品详情聚合接口吞吐量仅 400 QPS(目标 2000 QPS)。现场分析根因是多个微服务串行聚合且伴随复杂计算。我果断放弃高风险的重写逻辑,采取应急方案:在应用层引入 Guava 本地缓存加 10 秒短 TTL,前置阻断 80% 重复读,并配置熔断开关将次要的个性化推荐模块降级为静态兜底数据,将 QPS 瞬间拉升至 3200。业务平稳过峰后,我们在下个迭代用 CompletableFuture 异步并发聚合彻底根治。
① 简历常见平淡回答
「有一次上线后接口报错报警,我发现少写了个判断,紧急改完热修上线了。」
为什么不够:止损意识薄弱,急于现场排查修改是生产大忌。面试官看重的是以业务止损为首要优先级的事故处理规范(回滚优先)、清晰的团队同步机制与闭环根因复盘。
② 面试官追问逻辑
③ 深挖后可量化的高分示范
处理生产事故必须恪守「先止血、再排查、后根治」铁律。某次我发布用户资料改版,上线 3 分钟内错误率飙升至 5% 触发 P2 告警。我第一反应绝非登录服务器看代码调试,而是立即在发布系统一键回滚至上一稳定版本,70 秒内全量恢复服务止血。止血后拉取日志复盘,发现是老用户历史头像字段在旧版本存了空字符串导致反序列化 NPE,测试用例未覆盖极寒历史数据。我们随后补齐边缘单测,推进 CI 流水线集成针对老版本数据兼容性的自动化回归集,并坦承事故责任沉淀了团队防御复盘文档。
① 简历常见平淡回答
「改接口时一般不删老字段,加新字段就行,或者接口路径上加 v1、v2 版本号。」
为什么不够:回答流于概念。客户端(尤其是移动端 App 与第三方开放平台)版本碎片化严重,无法强制用户即时更新。面试官想听字段演进的防御性约定、序列化宽容度以及废弃接口的安全下线流程。
② 面试官追问逻辑
③ 深挖后可量化的高分示范
对外接口的向后兼容是架构设计的底线,我们推行三大工程法则:第一,字段只增不改,废弃字段保留且永不复用原字段名,枚举反序列化必须配未知兜底(UNKNOWN)防崩溃;第二,新增入参绝不允许设为必填,必须配有合理的默认缺省逻辑,若业务强依赖新入参,则以重载新接口或派生新 API 方式演进;第三,大版本断代重构才升级路由(如 `/v2/`),对废弃接口在网关层做埋点监控日志,当老版本调用流量低于 0.05% 并经过两轮邮件预警后,才最终执行物理下线,保证数百万存量客户端无感升级。