字节跳动后端一面:MySQL、Java多线程与Spring循环依赖

字节跳动 · 后端开发 · 一面 · 2026-04

面试题目

数据库

  1. MySQL 事务隔离级别有哪些?
  2. 讲一下脏读、幻读、不可重复读的区别?
  3. MySQL 索引的工作原理是什么?
  4. 为什么使用 B+ 树而不用 B 树?
  5. 联合索引是怎么实现的?

Java & 并发

  1. 线程和进程的区别?
  2. Java 线程之间如何传递参数?

Spring

  1. Spring 如何解决循环依赖?

算法

  • 打家劫舍变种(题目较短但情况较多,难度较高)

参考解析

1. MySQL 事务隔离级别

共四个级别(由低到高):READ UNCOMMITTEDREAD COMMITTEDREPEATABLE READ(MySQL 默认)、SERIALIZABLE。 级别越高并发性能越低,但数据一致性越强。MySQL InnoDB 默认使用 REPEATABLE READ,并通过 MVCC + Gap Lock 额外解决了幻读问题。

2. 脏读、不可重复读、幻读区别

问题描述解决隔离级别
脏读读到其他事务未提交的数据READ COMMITTED
不可重复读同一事务内两次读同一行,结果不同(被修改/删除)REPEATABLE READ
幻读同一事务内两次查询,结果集行数不同(被插入新行)SERIALIZABLE(或 InnoDB 的间隙锁)

3. MySQL 索引工作原理

InnoDB 使用 B+ 树存储索引,主键索引(聚簇索引)叶子节点存储完整行数据,二级索引叶子节点存储主键值。 查询时先走二级索引找到主键,再回表查聚簇索引(称为”回表”)。覆盖索引可避免回表。

4. B+ 树 vs B 树

  • B+ 树非叶子节点只存键不存数据,同等页大小能存更多键,树更矮,IO 次数更少。
  • B+ 树叶子节点用链表相连,范围查询只需遍历链表,效率远高于 B 树的中序遍历。
  • B 树每个节点都存数据,不适合范围扫描,且节点存储数据后分叉数减少,树更高。

5. 联合索引实现

联合索引本质仍是一棵 B+ 树,键为多列拼接后按最左前缀原则排序。 例如 (a, b, c) 联合索引,先按 a 排序,a 相同再按 b,b 相同再按 c。 查询必须从最左列开始连续匹配,跳过中间列则后续列无法走索引。

6. 线程与进程区别

  • 进程是资源分配的基本单位,拥有独立内存空间;线程是CPU调度的基本单位,共享进程内存。
  • 线程切换开销小于进程切换;进程崩溃不影响其他进程,线程崩溃可能导致整个进程退出。
  • 线程间通信比进程间通信更便捷(共享堆内存),但需注意同步问题。

7. Java 线程间参数传递

  • 共享变量:通过成员变量或 volatile/AtomicXxx 传递,需注意可见性与线程安全。
  • 构造函数/Callable:创建线程时通过构造参数传入,Callable + Future 可获取返回值。
  • ThreadLocal:每个线程独立副本,父子线程可用 InheritableThreadLocal 传递。
  • 阻塞队列:生产者-消费者模式下通过 BlockingQueue 传递数据。

8. Spring 解决循环依赖

Spring 通过三级缓存解决单例 Bean 的构造后循环依赖:

  1. 一级缓存(singletonObjects):存放完全初始化的 Bean。
  2. 二级缓存(earlySingletonObjects):存放提前暴露的早期引用(已实例化但未填充属性)。
  3. 三级缓存(singletonFactories):存放 ObjectFactory,用于生成早期引用(支持 AOP 代理)。

注意:构造器注入的循环依赖无法解决@Prototype 作用域的循环依赖也无法解决。

算法:打家劫舍变种

经典打家劫舍(LeetCode 198)核心是 DP:dp[i] = max(dp[i-1], dp[i-2] + nums[i])。 变种常见形式:环形(LC 213,拆成两段分别DP取最大)、树形(LC 337,树形DP)、带冷冻期等。 建议先明确约束条件,画出状态转移方程,再处理边界情况。