水滴 AI 全栈(北京)一二面:从 InnoDB 索引问到 Agent 中断恢复
- 轮次
- 一面、二面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
一面
- 做一下自我介绍。
- 基于实习经历介绍一下你做的项目以及负责的内容。
- 主流编码语言是 Python 还是 Java?
- 挑一段你觉得有代表性、有难度的实习经历讲一讲。
- 优化的具体是流程还是技术手段?是编码层面还是选型层面?优化前什么样、优化后达到什么效果?
- 这个项目是企业内部使用的吧?用户量大概多少?
- 这个 Agent 是公司原来就有、你在原基础上做迭代?原本的底座是自研还是用现成的?
- 大三开始就全程实习了?
- 除了这两段 Agent 实习,之前有没有做过传统 Web 开发、Java 写的系统?
- 简历提到了解 MySQL,先讲 InnoDB 的聚簇索引和非聚簇索引。
- 一个 user 表执行
select * from user where name = '张三',底层数据读取是一个什么过程? - 这个条件上有索引,结合刚才讲的索引,具体是怎么实现的、细一点?
- 讲一讲回表是什么意思?
- 有没有什么办法让它不回表?
- 索引用的是 B+ 树,为什么用 B+ 树,用哈希、B 树行不行?
- MySQL 默认隔离级别是什么?
- MySQL 的 ACID、MVCC 了解吗?简单讲一讲。
- 你用 FastAPI 做异步开发,先说进程、线程、协程分别是什么意思。
- Python 有 GIL 做多线程限制,你了解吗?
- 并发跟并行有什么区别?
- 你的智能问答 Agent 多个人同时问答,这算并行还是并发?多线程怎么考虑的?
- 这个过程是 IO 密集还是 CPU 密集?为什么、怎么划分?
- 你写了 Redis 分布式锁的实战经验,哪个场景用到了?
- 锁是你实现的吗?展开细节:为什么用锁、为了防重、过期时间怎么考虑、过程中断了怎么办?
- 如果不用 Redis,它本身能实现幂等吗?
- 你是直接通过大模型去解析 PDF 吗?
- 回到 Redis,缓存击穿、穿透、雪崩展开讲一讲。
- 布隆过滤器「判断没有就一定没有、判断有不一定有」,为什么?
- 这几段实习是全程负责一个项目,还是多个项目打杂式参与?
- 这几个项目从编码完成到上线的整个流程是什么节奏?
- 代码 review 这块 mentor 会跟着你过吗?
- 目前开发是纯 AI coding 还是手搓?
- 目前用什么 AI coding 工具?
- 大概什么时候接触 AI coding?一个月花多少钱?
- 豆包这类对话式 AI 和 Cursor、Claude Code 这类编码式 AI,从研发视角看有什么本质区别?
- 实习里的 Agent,你有没有直接写 CLI、Function Calling、MCP、做工具搭建,而不只是写 skill?
- 暂停等待恢复这个中断机制,详细讲一下怎么设计、数据怎么流转。
- 你们用 WebSocket 还是 SSE?
- 网页对话时刷新浏览器会出现什么情况?
- RAG 相关了解多深?LLM wiki 和 RAG 的区别以及适用场景?
- 企业内部知识库遇到高频更新的热点知识(今天的代码规范明天就被覆盖),在 Agent 的记忆里该怎么做?以 RAG 为例是怎样的变化过程?
二面
- 自我介绍。
- 有没有了解过实习的公司?除了 Agent 用的语言,平常正常业务流程用的是什么?
- 实习项目的数据来源是哪?
- 分析成果无法复用是怎么解决的?
- 这个项目的结果怎么评估?达到目标是什么指标?
- 采纳了哪些分析结果?采纳或不采纳有统计吗?
- 项目用的模型有没有评测过?Claude 和其他模型效果差异大不大?
- 多模态这块对比了哪些模型?
- 实现幂等写入和单游戏并发锁,是基于什么背景做的?
- 如果用 Redis 做分布式锁,需要注意哪些设计缺陷?
- Python 的线程和进程跟 Java 有什么区别?
- 怎么把控 Python 工程化代码的健壮性?
- 它怎么发挥 CPU 性能?架构是否稳定?
- 几家实习公司做的 Agent 整体技术上有什么差异?优缺点对比。
- 这些项目的数据量大概有多少?
- 数据库性能出过问题吗?
- 有没有想过之后开发中需要注意哪些东西?
- MySQL 的索引和存储引擎数据结构有什么关系?
- Java 的 HashMap 数据结构了解吗?链表加红黑树是怎么设计的?为什么这里不用 B+ 树、B 树?
- 平时使用 Redis 需要注意哪些问题?
- 你对哪个 MQ 比较熟?消费端出现积压怎么排查?
- Java 里线程池自带的那几个创建方法能直接用吗?
- 用 Spring Cloud 做微服务平台一般需要哪些组件?
- 注册中心一般遵循什么原则?可用性、数据一致性方面有哪些设计原则?
- 突然接触一门不熟的语言,怎么把控写代码的质量?
《参考解析》
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 频率)、主从延迟(写完立刻读可能读不到)、以及连接池与超时配置。运维层面看慢日志、监控命中率与内存碎片率。