快手 C++开发 一面:MySQL 优化边界与 shared_ptr 线程安全
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 自我介绍。
- 介绍一下简历上第一个项目。
- MySQL 应该能抗住这些流量,为什么要优化?
- 如果一个 C++ 服务出现内存持续上涨,你会怎么判断是内存泄漏还是内存碎片?
- 产品形态是怎样的?
- C++ 中 shared_ptr 的引用计数如何实现?为什么它在线程安全方面仍然存在问题?
- 再讲一下你简历上另一个项目。
- MySQL 的 MVCC 是怎么实现的?为什么 RR 隔离级别下仍然可能出现幻读问题?
《参考解析》
MySQL 能不能抗住流量,不能只看 QPS,要看 SQL 的实际执行成本和尾延迟。 一个系统只有两千 QPS 看起来不高,但如果大部分请求要扫描几十万行、再排序回表,数据库承担的随机 I/O 和 CPU 开销可能已经很高。InnoDB 还要看 Buffer Pool 命中率:热点索引页在内存里时很快,一旦有效工作集超过 Buffer Pool,随机磁盘读就会明显增加。另一个隐蔽问题是连接池排队,假设每条 SQL 平均 5ms、连接数 20,理想吞吐上限大约 4000 QPS(1000ms / 5ms × 20),但这只是忽略锁竞争和其他开销的估算;慢查询让连接占用时间变长,排队延迟会迅速放大。所以优化目标不是把最大 QPS 拉高,而是在业务峰值下保证稳定的 P95、P99 并留出容量余量。排查顺序一般是慢查询日志、EXPLAIN ANALYZE、行锁等待、Buffer Pool 读请求和磁盘 I/O,再判断是索引、SQL 写法还是连接管理的问题。
内存持续上涨要先证明「涨的是什么」,再谈泄漏还是碎片。 RSS 只是进程驻留的物理页,受分配器缓存、内存碎片、mmap 映射和真实业务对象增长共同影响,不能一看到曲线向上就判定泄漏。排查时同时看 RSS、虚拟内存、匿名内存、堆使用量和业务请求量,判断增长是否符合负载预期;怀疑泄漏就用 AddressSanitizer、LeakSanitizer 或 Heap Profiling,看哪些分配调用栈持续增长且没有释放。C++ 里常见的是 shared_ptr 循环引用,两个对象互相持有强引用,计数永远不归零,需要用 weak_ptr 打破所有权环。碎片则是另一类问题:频繁分配不同大小的对象,即使部分内存已释放,分配器也可能因为空闲块不连续或内存页未归还而让 RSS 居高不下;如果 Heap Profile 显示存活对象没明显增加、RSS 却很高,就要看分配器行为、线程缓存和碎片,而不是继续翻业务对象生命周期。
Agent 的产品形态决定了后端接口怎么设计。 如果交付成企业内部 Web 工作台,用户用自然语言查研发文档、接口规范和历史故障,前端就不能只显示聊天气泡,还要显示当前执行的任务、检索引用和工具调用状态。后端要把模型生成的 token 流和业务事件分开:tool_started、tool_finished、message_delta、task_failed 这类结构化事件让前端知道每一步在做什么,而不是把一切都塞进自然语言。企业知识问答里引用来源往往比措辞更重要,用户需要判断答案是不是来自可信文档;再配会话历史、用户反馈和权限控制,才是一个能落地的产品。
shared_ptr 的引用计数在线程安全上只解决「计数」这一件事。 它通过控制块维护强引用计数、弱引用计数和删除器,多个 shared_ptr 副本共享同一个控制块时,计数的增减要用原子操作,避免并发复制和析构把计数改坏。但计数原子不等于被管理对象线程安全:两个线程各持有一个 shared_ptr 副本,同时对同一个 vector 做 push_back,仍然是数据竞争;同一个 shared_ptr 对象被多个线程无同步地读写,也不因为控制块计数是原子的就安全,需要外部同步或 C++20 的 std::atomic<std::shared_ptr<T>>。开销也不能忽略:高并发下频繁复制 shared_ptr 会带来原子读改写和缓存一致性流量,热点控制块可能成为瓶颈。所以明确所有权时优先 unique_ptr,确实需要共享所有权再用 shared_ptr,创建时尽量 make_shared 让控制块和对象一次分配。
MVCC 靠隐藏事务字段、Undo Log 版本链和 Read View 实现快照读。 InnoDB 行里有 DB_TRX_ID 和 DB_ROLL_PTR,更新时旧版本写进 Undo Log,回滚指针把版本串成链;事务第一次快照读时生成 Read View,记录活跃事务集合和最小事务 ID,读取时沿着版本链找到对当前事务可见的那个版本。RR 下普通 SELECT 用同一个 Read View,所以重复读能看到一致快照,不会看到别的事务新插入的行。但「RR 不会幻读」只对快照读成立:SELECT ... FOR UPDATE、UPDATE、DELETE 是当前读,读的是最新已提交版本;InnoDB 用 Next-Key Lock(记录锁加间隙锁)锁住扫描范围来阻止插入,可间隙锁的效果依赖索引和查询计划,范围没锁住、或先快照读后又做当前读,仍可能看到或影响到新行。更准确的说法是:MVCC 解决一致性读,锁解决当前读的并发,RR 并不等于可串行化。