面灵AI→

水滴 AI 全栈(北京)一二面:从 InnoDB 索引问到 Agent 中断恢复

轮次
一面、二面
时间
2026-10
来源
牛客网

《面试题目》

一面

  1. 做一下自我介绍。
  2. 基于实习经历介绍一下你做的项目以及负责的内容。
  3. 主流编码语言是 Python 还是 Java?
  4. 挑一段你觉得有代表性、有难度的实习经历讲一讲。
  5. 优化的具体是流程还是技术手段?是编码层面还是选型层面?优化前什么样、优化后达到什么效果?
  6. 这个项目是企业内部使用的吧?用户量大概多少?
  7. 这个 Agent 是公司原来就有、你在原基础上做迭代?原本的底座是自研还是用现成的?
  8. 大三开始就全程实习了?
  9. 除了这两段 Agent 实习,之前有没有做过传统 Web 开发、Java 写的系统?
  10. 简历提到了解 MySQL,先讲 InnoDB 的聚簇索引和非聚簇索引。
  11. 一个 user 表执行 select * from user where name = '张三',底层数据读取是一个什么过程?
  12. 这个条件上有索引,结合刚才讲的索引,具体是怎么实现的、细一点?
  13. 讲一讲回表是什么意思?
  14. 有没有什么办法让它不回表?
  15. 索引用的是 B+ 树,为什么用 B+ 树,用哈希、B 树行不行?
  16. MySQL 默认隔离级别是什么?
  17. MySQL 的 ACID、MVCC 了解吗?简单讲一讲。
  18. 你用 FastAPI 做异步开发,先说进程、线程、协程分别是什么意思。
  19. Python 有 GIL 做多线程限制,你了解吗?
  20. 并发跟并行有什么区别?
  21. 你的智能问答 Agent 多个人同时问答,这算并行还是并发?多线程怎么考虑的?
  22. 这个过程是 IO 密集还是 CPU 密集?为什么、怎么划分?
  23. 你写了 Redis 分布式锁的实战经验,哪个场景用到了?
  24. 锁是你实现的吗?展开细节:为什么用锁、为了防重、过期时间怎么考虑、过程中断了怎么办?
  25. 如果不用 Redis,它本身能实现幂等吗?
  26. 你是直接通过大模型去解析 PDF 吗?
  27. 回到 Redis,缓存击穿、穿透、雪崩展开讲一讲。
  28. 布隆过滤器「判断没有就一定没有、判断有不一定有」,为什么?
  29. 这几段实习是全程负责一个项目,还是多个项目打杂式参与?
  30. 这几个项目从编码完成到上线的整个流程是什么节奏?
  31. 代码 review 这块 mentor 会跟着你过吗?
  32. 目前开发是纯 AI coding 还是手搓?
  33. 目前用什么 AI coding 工具?
  34. 大概什么时候接触 AI coding?一个月花多少钱?
  35. 豆包这类对话式 AI 和 Cursor、Claude Code 这类编码式 AI,从研发视角看有什么本质区别?
  36. 实习里的 Agent,你有没有直接写 CLI、Function Calling、MCP、做工具搭建,而不只是写 skill?
  37. 暂停等待恢复这个中断机制,详细讲一下怎么设计、数据怎么流转。
  38. 你们用 WebSocket 还是 SSE?
  39. 网页对话时刷新浏览器会出现什么情况?
  40. RAG 相关了解多深?LLM wiki 和 RAG 的区别以及适用场景?
  41. 企业内部知识库遇到高频更新的热点知识(今天的代码规范明天就被覆盖),在 Agent 的记忆里该怎么做?以 RAG 为例是怎样的变化过程?

二面

  1. 自我介绍。
  2. 有没有了解过实习的公司?除了 Agent 用的语言,平常正常业务流程用的是什么?
  3. 实习项目的数据来源是哪?
  4. 分析成果无法复用是怎么解决的?
  5. 这个项目的结果怎么评估?达到目标是什么指标?
  6. 采纳了哪些分析结果?采纳或不采纳有统计吗?
  7. 项目用的模型有没有评测过?Claude 和其他模型效果差异大不大?
  8. 多模态这块对比了哪些模型?
  9. 实现幂等写入和单游戏并发锁,是基于什么背景做的?
  10. 如果用 Redis 做分布式锁,需要注意哪些设计缺陷?
  11. Python 的线程和进程跟 Java 有什么区别?
  12. 怎么把控 Python 工程化代码的健壮性?
  13. 它怎么发挥 CPU 性能?架构是否稳定?
  14. 几家实习公司做的 Agent 整体技术上有什么差异?优缺点对比。
  15. 这些项目的数据量大概有多少?
  16. 数据库性能出过问题吗?
  17. 有没有想过之后开发中需要注意哪些东西?
  18. MySQL 的索引和存储引擎数据结构有什么关系?
  19. Java 的 HashMap 数据结构了解吗?链表加红黑树是怎么设计的?为什么这里不用 B+ 树、B 树?
  20. 平时使用 Redis 需要注意哪些问题?
  21. 你对哪个 MQ 比较熟?消费端出现积压怎么排查?
  22. Java 里线程池自带的那几个创建方法能直接用吗?
  23. 用 Spring Cloud 做微服务平台一般需要哪些组件?
  24. 注册中心一般遵循什么原则?可用性、数据一致性方面有哪些设计原则?
  25. 突然接触一门不熟的语言,怎么把控写代码的质量?

《参考解析》

InnoDB 的两种索引与回表。 聚簇索引就是主键索引,叶子节点直接存整行数据,所以「按主键查」一次 B+ 树查找就拿到行;非聚簇(二级)索引的叶子节点存的是「索引列 + 主键值」,要拿其他列就得再回聚簇索引查一次,这就是回表。select * from user where name='张三' 若 name 上有索引,过程是:走 name 的二级索引定位到叶子 → 取到主键 id → 用 id 再走一次聚簇索引取整行。避免回表的手段是覆盖索引:查询列全部包含在索引里(select id,name from user where name=?,或建 (name, age) 复合索引后只查 name、age),此时优化器直接用二级索引返回,不再回表。EXPLAIN 里 Extra: Using index 就是覆盖索引生效的标志。

为什么是 B+ 树。 三层原因:① 矮胖——非叶子节点只存键和指针,一个 16KB 页能放几百个键,三到四层就能覆盖上亿行,磁盘 IO 次数少且稳定;② 范围查询友好——叶子节点用双向链表串联,between、order by、limit 只需顺着链表扫,B 树要在树上反复回溯;③ 数据都在叶子、查询路径长度一致,性能可预测。哈希索引只支持等值、不支持范围和排序,且无法应对冲突与最左前缀;B 树非叶子也存数据,单页能放的键更少、树更高,范围扫描还要中序遍历。

MVCC 与隔离级别。 InnoDB 默认 REPEATABLE READ。MVCC 靠三样东西:隐藏列(DB_TRX_ID 最近修改事务、DB_ROLL_PTR 指向 undo log)、undo log 里的版本链、以及 ReadView(记录当前活跃事务 ID 集合)。快照读时沿版本链找到「对当前 ReadView 可见」的第一个版本,从而实现「同一事务内多次读一致」;当前读(select ... for update、加锁写)读最新版本。RR 下间隙锁(Next-Key Lock)负责防幻读。ACID 的落点:原子性靠 undo log、持久性靠 redo log(WAL + 刷盘策略)、隔离性靠锁与 MVCC、一致性是前三者加上业务约束的结果。

进程、线程、协程与 GIL。 进程有独立地址空间,是资源分配单位;线程共享进程内存,是 CPU 调度单位,切换要陷入内核;协程是用户态的轻量任务,由程序自己调度,切换只保存少量寄存器与栈,适合大量 IO 等待。Python 的 GIL 让同一进程内同一时刻只有一个线程执行字节码,所以多线程对 CPU 密集无加速,但对 IO 密集有效(等待时会释放 GIL);要吃满多核就用多进程,要高并发 IO 就用 asyncio 协程。并发是「同时处理多个任务」(可以交替进行),并行是「同一时刻真正同时执行」,需要多核。智能问答 Agent 属于 IO 密集(等模型流式返回、等数据库、等外部 API),用协程最划算:单进程能挂成千上万个等待中的请求,线程池只适合少量阻塞型依赖。区分方法看时间花在「算」还是「等」:CPU 占用高是计算密集,等待占比高是 IO 密集。

Redis 分布式锁的设计要点。 场景通常是防重复提交/防并发写同一资源(比如同一个游戏房间的并发结算)。四个必须讲清的点:① 加锁必须原子——SET key value NX PX 30000,value 用唯一请求 ID,避免「A 的锁过期后被 B 拿到、A 又删了 B 的锁」;② 过期时间要大于业务最长执行时间,并配看门狗续期(Redisson 的 watchdog),续期也要校验持有者;③ 释放必须原子且校验持有者——用 Lua 脚本「比对 value 相等才 del」;④ 失败与中断——拿不到锁要有超时与重试退避,进程崩溃时靠 TTL 自动释放,业务侧再用幂等键兜底(唯一索引、状态机、去重表)。不用 Redis 也能做幂等:数据库唯一约束 + insert ignore、状态机「只有待支付才能改成已支付」、业务去重表,这些是最终防线;Redis 锁只是减少并发冲突、提升体验,不能当成唯一保证。还要提一句单点与主从切换的锁丢失问题(Redlock 的争议),生产上通常配合「锁 + 幂等」而不是只信锁。

缓存三兄弟与布隆过滤器。 穿透:查不存在的数据,缓存和 DB 都没有,请求全打到 DB——解法是缓存空值(短 TTL)、布隆过滤器前置拦截、参数校验。击穿:某个热点 key 过期瞬间大量请求同时回源——解法是互斥锁/单飞(只让一个请求回源,其他等待)、逻辑过期(后台异步重建)、热点永不过期。雪崩:大量 key 同时过期或缓存集群故障——解法是过期时间加随机抖动、多级缓存、限流降级、集群高可用。布隆过滤器的原理是一个位数组加多个哈希函数:插入时把 k 个位置置 1,查询时若有一位是 0,说明一定没插入过(一定不存在);若全是 1,可能是别的元素把这些位碰巧置 1 了(假阳性),所以说「有不一定有」。它不支持删除(计数布隆可以),也不精确。

Agent 的中断与恢复。 核心是把「执行」变成可持久化的状态机:每一步把 sessionId、当前节点、已完成节点的输出、待确认的用户输入写进存储(DB/Redis),并给每次执行一个 runId。用户暂停时保存 checkpoint 并释放模型连接;恢复时按 runId 读回状态、从断点续跑,已完成的工具调用直接复用结果。要处理三件事:幂等(重复恢复不重复产生副作用,用输入哈希 + 唯一约束)、超时与孤儿(超过 TTL 的 run 标记为过期,不占资源)、版本一致(提示词/工具变更后旧 checkpoint 要么兼容要么显式作废)。传输层选型:WebSocket 是全双工长连接,适合实时语音与双向打断;SSE 是服务端单向推送、基于 HTTP、自动重连,适合「只推流给前端」的对话场景。刷新浏览器会断开连接,若状态只存在内存里就会丢;所以要把会话状态落库、前端带 sessionId 重连后拉历史与当前进度,服务端支持「重连后继续推流」,而不是从头再来。

LLM wiki 与 RAG 的区别。 RAG 是「每次提问时检索相关片段拼进上下文」,优点是知识可随时更新、可溯源,缺点是对多跳推理和全局性问题弱、检索质量决定上限;LLM wiki(把知识预先整理成结构化条目再喂给模型/写进系统提示)是「把知识压缩成一份可维护的文档」,优点是稳定、省 token、适合固定领域的常识,缺点更新成本高、容易过期。实际系统常常混用:稳定的规范类知识进 wiki/系统提示,变动频繁的事实进 RAG 检索。

高频更新的热点知识怎么处理。 关键是把「更新」与「生效」解耦:文档带版本号与生效时间,索引侧做增量更新(新版本写入、旧版本标记失效),检索时按 asOf 或版本过滤,避免新旧混检;对「必须立刻生效」的规则类知识(代码规范、合规条款),走「结构化配置 + 每次请求注入」而不是等索引刷新;再配一个变更通知,让缓存与向量库在同一事务边界内更新。RAG 的变化过程是:文档更新 → 解析切分 → 重新 embedding → 写入新版本 → 旧版本下线,这中间有窗口期,所以对强一致要求高的知识不能只靠 RAG。

MQ 积压排查。 先分方向:生产端突增(流量、批量任务、重试风暴)还是消费端变慢(下游超时、单条处理变慢、消费线程挂住、rebalance 频繁)。看指标:各分区的 lag 分布(是否倾斜)、消费 TPS vs 生产 TPS、消费耗时 P99、有无重复消费与死信。常见处置:扩容消费者(不超过分区数)、提升单批拉取与并发、把慢逻辑异步化、对非关键消息降级/丢弃到死信、临时把积压消息转到旁路主题由专门任务消费。根治要看是不是分区键倾斜或下游容量不足。

Java 线程池与 Spring Cloud。 Executors 的四个快捷方法不建议直接用:newFixedThreadPool/newSingleThreadExecutor 用无界队列会 OOM,newCachedThreadPool 最大线程数是 Integer.MAX_VALUE 会创建过多线程,newScheduledThreadPool 同样队列无界。应该手写 ThreadPoolExecutor,明确核心数、最大数、有界队列、线程工厂(带名字便于排查)和拒绝策略(Abort 抛异常、CallerRuns 回压、Discard 丢弃、DiscardOldest 丢最老)。Spring Cloud 一套通常包括:注册与发现(Nacos/Eureka)、配置中心、网关(Gateway)、负载均衡(LoadBalancer)、熔断限流(Sentinel/Resilience4j)、链路追踪(Sleuth/Micrometer Tracing)、消息与分布式事务。注册中心遵循的是 AP 或 CP 的取舍:Eureka 偏 AP(可用性优先、最终一致),Nacos 可切 AP/CP,ZooKeeper 是 CP(选举期间不可写)。答这题时把「服务发现要的是可用性,短暂看到旧实例列表比整个不可用更好」讲出来,就能对上「设计原则」的追问。

新手语言怎么控质量。 三条:靠工具——类型检查、lint、格式化、单测覆盖率门禁;靠约定——把项目规范写进仓库(目录结构、错误处理、日志规范),照着已有代码写;靠评审——小步提交、每 PR 只做一件事、请熟手重点看边界与错误分支。再加一条「先写测试再写实现」,在自己不熟的生态里,测试是唯一能快速确认「我理解对了」的手段。

AI coding 的两种形态。 对话式 AI(豆包这类)是「问答 + 生成片段」,上下文靠人搬运,产出需要人判断与拼装;编码式 Agent(Cursor、Claude Code)能读仓库、跨文件改代码、跑命令、看测试结果,是「带工具的闭环执行者」,所以它的价值在「能自己验证」,风险在「改得太多、改错地方」。从研发视角看,前者替代的是「查文档」,后者替代的是「搬砖 + 试错」;用好后者的关键是把验收标准写成可执行的检查,而不是靠肉眼读 diff。

评估与采纳。 项目评估要落到指标:准确率/召回、人工采纳率、平均处理时长、节省的人力。采纳率要能分层(按业务、按难度、按模型版本),否则「老板觉得没用」时无法解释。模型对比要做同题同提示的 A/B,记录失败类型(事实错、格式错、漏字段),只看平均分容易掩盖差异。

Redis 在生产上要注意的。 大 key(单个 value 过大导致阻塞与网络抖动)、热 key(打满单分片,要本地缓存或拆分)、过期策略与内存淘汰(maxmemory-policy 选错会误删)、持久化(RDB 丢数据窗口、AOF 的 fsync 频率)、主从延迟(写完立刻读可能读不到)、以及连接池与超时配置。运维层面看慢日志、监控命中率与内存碎片率。