面试
- 面试复盘
-
- 一、技术基础与框架 (Java/Python/Spring)
- 二、数据库 (MySQL)
-
-
- 聚簇索引 (Clustered Index)
- [② 二级索引 / 辅助索引 (Secondary Index)](#② 二级索引 / 辅助索引 (Secondary Index))
- [🔹 数据量过大 (存储瓶颈)](#🔹 数据量过大 (存储瓶颈))
- [🔹 并发量过高 (性能瓶颈)](#🔹 并发量过高 (性能瓶颈))
- [🔹 大字段/热点行冲突](#🔹 大字段/热点行冲突)
- [2. 分库分表带来的巨大代价(为什么不轻易分?)](#2. 分库分表带来的巨大代价(为什么不轻易分?))
-
- [❌ 1. 跨库 Join 失效](#❌ 1. 跨库 Join 失效)
- [❌ 2. 分布式事务难题](#❌ 2. 分布式事务难题)
- [❌ 3. 排序、分页、聚合函数变复杂](#❌ 3. 排序、分页、聚合函数变复杂)
- [❌ 4. 扩容迁移极其痛苦](#❌ 4. 扩容迁移极其痛苦)
- [❌ 5. 唯一主键生成](#❌ 5. 唯一主键生成)
- [❌ 6. 运维监控困难](#❌ 6. 运维监控困难)
- 优化分页 (`LIMIT`)
- 三、中间件与分布式 (RocketMQ/Redis)
-
- 四、前端与运维 (Vue/Docker/Linux)
- 五、项目经验与业务场景
面试复盘
原来我的努力仍然不够
一、技术基础与框架 (Java/Python/Spring)
-
Python相关:
-
你自学过Python,那什么是协程?有没有写过协程相关的代码?
协程是用户态的轻量级线程,在单线程内执行,和多线程相比避免了操作系统层面的线程的上下文切换,适合高并发IO。在python中是使用asyncio库中await和async关键字,async是标识这个函数是协程函数,通过await挂起当前协程,asyncio.run()执行,在asyncio是通过event Loop维护了一个任务队列,不断轮询,检查哪些协程的IO执行完成,哪些定时器到了,满足条件会唤醒对应的协程,如果没有任务他不会阻塞等待,不占用CPU。如果一个协程函数堵塞了会妨碍继续执行
适合大量I/O密集型任务,比如: 爬虫、文件读写,少量计算,大量I/O的场景,协程可以提高效率,减少资源占用。
-
在Python中使用装饰器主要是用来做什么的?(如登录校验、前置操作)
日志记录,性能计时,权限校验和认证,类型检查和数据验证等
-
Java中有没有类似Python装饰器的做法?(引导至注解/AOP)
注解、AOP(面向切面编程)、动态代理(
@Aspect,@Around),无法处理final方法。 -
Spring的AOP动态代理使用了哪些库?(JDK动态代理 vs CGLIB)
-
-
Spring Boot/Cloud:
-
你们的系统是分布式的吗?如果是集群部署,设计编码时需要特别注意什么?(服务保护、熔断降级、时钟同步)
状态管理:会话共享、分布式缓存、文件存储、全局锁、唯一ID
定时任务:分布式锁,唯一索引或者乐观锁
并发和幂等:数据库:唯一索引和乐观锁,业务:状态检查,令牌机制(短期token,防重复操作),去重表(请求ID)
日志和监控:链路追踪,集中式日志,
配置:配置中心中,
通信容错:超时控制,重试机制,熔断降级(快速返回),异步解耦:对非核心流程使用消息队列异步处理
-
服务器时钟不同步对支付业务有什么影响?如何解决?(接口幂等性、时间校验)
会发生重复请求的可能,导致数据出错。
强制时间:同步部署chrony服务,选取一个NTP源作为标准时间,其他时间从上面同步
不使用本地时间:使用数据库的时间为准,或者使用分布式时钟服务(Google TrueTime思想,基于raft协议的时间服务)
幂等处理:特定情况下:增加一张表,如请求ID,重复请求出错,忽略时间影响。状态检查
放宽时间检查(防重放机制)或者进行系统自检(调用标准时间接口或内部NTP源)
-
项目中是否做过异步任务?是如何实现的?(
@Async注解)1.
ThredPoolExcutor线程池,如果直接new Thread()会导致资源不可控,容易OOM。2.标准封装:CompletableFuture,需要等待多个异步任务完成或任务之间有依赖关系
3.@Async。自动提交到定义的线程池中,配置类加@EnableAsync
- 同类调用失效 :在同一个类中,A 方法调用 B 方法(B 有
@Async),B 不会异步执行!因为绕过了代理对象。必须注入自身或使用 AspectJ。 - 事务失效 :
@Async方法上的@Transactional通常不生效(因为不在同一个事务线程中)。 - 异常吞没 :
void方法的异常默认会被吞掉,需实现AsyncUncaughtExceptionHandler。
4.消息队列:架构级异步,解耦,MQ会自动重试
5.响应式编程:使用少量线程通过海量回调处理并发?极高并发,适合IO密集型,网关,即时通讯
6.虚拟线程:JVM管理轻量级线程,(Thread.sleep())
异步中的上下文传递:主线程取值,阿里的TTL,自动把主线程的值传给子线程
- 同类调用失效 :在同一个类中,A 方法调用 B 方法(B 有
-
异步任务有没有单独配置线程池?为什么推荐手动配置?
1.避免OOM,内存溢出。应为默认线程池的容量很大,一旦处理速度小于请求提交速度,任务会源源不断进入队列,导致堆溢出。
2.明确拒绝策略:1.直接抛出异常
AbordPolicy,2.直接将任务交给提交任务的线程去处理,降低第条任务的速度,给线程池缓冲时间3.丢弃策略,不抛异常,不通知,
4.丢弃最旧策略
5.自定义拒绝策略 实现
RejectExcutionHandler(记录日志,发送警告,比如存到数据库等)
-
二、数据库 (MySQL)
-
MyBatis/MyBatis-Plus:
-
在MyBatis中如何进行动态条件拼接?(
#{}vs${}防止SQL注入)if标签(使用where 1=1 防止拼接and)where标签,choose、when、othorwise标签,trim标签,foreach标签
-
如果有两个参数,参数A为真时才拼接参数B,具体怎么写?(
<if test="">标签或Service层处理)同上,
-
如果有重复的SQL片段需要复用,怎么解决?(
<include>标签)sql标签,include引用
-
数据处理时有没有做前置或后置处理?(自动填充ID、时间、乐观锁版本号)
- 插件,@intercepts。前置@StatementHandler.prepare.@ParameterHandler.prepare。后置:@ResultSetHanlder
-
-
事务与锁:
-
MySQL有哪几种事务隔离级别?默认是什么?
隔离级别 脏读 (Dirty Read) 不可重复读 (Non-Repeatable Read) 幻读 (Phantom Read) 性能 读未提交 ✅ 可能发生 ✅ 可能发生 ✅ 可能发生 ⭐⭐⭐⭐⭐ (最高) 读已提交 (RC) ❌ 不会发生 ✅ 可能发生 ✅ 可能发生 ⭐⭐⭐⭐ 可重复读 (RR) ❌ 不会发生 ❌ 不会发生 ⚠️ 基本避免 (InnoDB 特有优化)(MVCC) ⭐⭐⭐ 串行化 ❌ 不会发生 ❌ 不会发生 ❌ 不会发生 ⭐ (最低) 注:
- 脏读:读到了未提交的数据。
- 不可重复读 :重点在更新。两次读同一行,值变了。
- 幻读 :重点在数量。两次读同一个范围,行数变了(通常由 insert/delete 导致)。
-
在你的项目中会选择哪种事务级别?为什么?(读已提交 vs 可重复读)
- 一致性更强 :对于很多业务逻辑(如
SELECT ... FOR UPDATE或复杂的业务计算),RR 能提供更强的一致性保障,避免逻辑错误。 - InnoDB 的优化:MySQL 团队通过 MVCC 和 Next-Key Lock 极大地缓解了 RR 级别的幻读问题,使得 RR 在高并发下的表现并不比 RC 差太多,同时提供了更好的数据安全性。
- 一致性更强 :对于很多业务逻辑(如
-
MySQL索引默认使用的数据结构是什么?(B+树,非红黑树)
B+树,B+ 树的 所有数据都存储在叶子节点 ,且叶子节点之间通过 双向链表 连接。
-
了解聚簇索引和非聚簇索引的区别吗?
聚簇索引 (Clustered Index)
- 定义:数据行本身就在 B+ 树的叶子节点上。
- 特点 :
- 每个表只能有一个聚簇索引(通常是主键 Primary Key)。
- 叶子节点存储的是 完整的行数据。
- 如果没有定义主键,InnoDB 会选择一个唯一非空索引,或者自动生成一个隐藏的
ROW_ID作为聚簇索引。
② 二级索引 / 辅助索引 (Secondary Index)
- 定义 :叶子节点存储的是 索引列的值 + 主键值。
- 特点 :
- 可以有多个。
- 查询过程(回表):先在二级索引 B+ 树中找到主键 ID,然后再拿着主键 ID 去聚簇索引 B+ 树中查完整数据。
- 覆盖索引:如果查询的字段刚好都在二级索引里,就不需要回表,效率极高。
-
你们项目做过分库分表吗?为什么?
通常当单表数据量或单库并发量达到以下瓶颈时,才考虑分库分表:
🔹 数据量过大 (存储瓶颈)
- 现象 :单表数据量超过 1000 万 ~ 2000 万 行(经验值,非绝对)。
- 后果 :
- B+ 树高度增加,磁盘 I/O 次数变多,查询变慢。
- DDL 操作(如加字段、加索引)耗时极长,甚至锁表导致业务停摆。
- 备份和恢复数据的时间窗口过长,风险极大。
🔹 并发量过高 (性能瓶颈)
- 现象:单机 QPS/TPS 达到数据库硬件极限(如 MySQL 单机写能力通常在 2000~5000 TPS 左右,读能力取决于配置和缓存)。
- 后果:CPU 飙升、连接数耗尽、大量请求排队等待,响应时间(RT)急剧拉长。
🔹 大字段/热点行冲突
- 现象:某些行包含巨大的文本(如文章内容),或者某一行被极高频率更新(如热门商品的库存扣减)。
- 后果:拖慢整个缓冲池(Buffer Pool),导致热点行锁竞争严重。
2. 分库分表带来的巨大代价(为什么不轻易分?)
在决定分库分表前,必须清楚它将引入哪些复杂性,这些往往是团队难以承受的:
❌ 1. 跨库 Join 失效
- 问题:分库后,数据分散在不同物理库,MySQL 原生不支持跨库 Join。
- 解决 :
- 应用层组装:查多次,在代码里拼(效率低,开发麻烦)。
- 字段冗余:把需要 Join 的数据复制一份存到当前表(数据一致性难维护)。
- 全局表:小表(如字典表)在每个库都存一份。
- 中间件支持:使用 ShardingSphere 等中间件进行绑定表或联邦查询(性能通常较差)。
❌ 2. 分布式事务难题
- 问题:原本在一个库里的原子操作(如 A 表扣钱,B 表加钱),分库后变成了跨库操作,本地事务失效。
- 解决:必须引入分布式事务方案(如 TCC、Saga、本地消息表 + 最终一致性、Seata 等),极大地增加了开发和维护成本。
❌ 3. 排序、分页、聚合函数变复杂
- 问题 :
ORDER BY,LIMIT,GROUP BY,COUNT,SUM等操作在跨库场景下无法直接执行。 - 解决 :需要在各个分片分别执行,然后在内存中归并排序或计算。
- 例如:
LIMIT 10000, 10可能需要从每个分片查前 10010 条数据,然后在内存排序取最后 10 条,性能极差(深分页问题)。
- 例如:
❌ 4. 扩容迁移极其痛苦
- 问题 :如果分片规则设计不好(如按用户 ID 取模分 4 库),当数据增长需要扩到 8 库时,数据需要重新分布。
- 解决:需要开发复杂的数据迁移工具,进行停机迁移或双写平滑迁移,风险极高。
❌ 5. 唯一主键生成
- 问题 :自增 ID (
AUTO_INCREMENT) 在多库中会重复。 - 解决:必须使用分布式 ID 生成算法(如雪花算法 Snowflake、号段模式等)。
❌ 6. 运维监控困难
- 问题:从 1 个库变成 几十甚至上百个库,定位问题、监控慢 SQL、管理连接池的难度成倍增加。
在走到分库分表这一步之前,通常有 80% 的问题可以通过以下方案解决:
- 索引优化:检查是否缺少索引、索引是否失效、是否走了全表扫描。这是性价比最高的优化。
- SQL 优化 :改写烂 SQL,避免
SELECT *,避免深分页,利用覆盖索引。 - 架构升级 :
- 读写分离:主库写,多个从库读,分担读压力。
- 引入缓存 (Redis):将热点数据放入缓存,挡掉大部分读请求。
- 历史数据归档:将 3 个月前的订单数据移动到"历史库"或冷存储(HBase/TiDB),保持主表轻量。
- 垂直拆分 :
- 垂直分库:按业务模块拆分(如用户库、订单库、商品库),不同服务连不同的库。
- 垂直分表 :将大字段(如
content文本)拆到扩展表中,主表只留核心字段,提高内存命中率。
-
-
优化与实战:
-
报表查询是如何优化的?(临时表、拆分大SQL、加索引)
explain, 索引,使用覆盖索引,防止回表查询
优化分页 (
LIMIT)深分页是性能杀手。
-
❌
LIMIT 1000000, 10(MySQL 会扫描前 1000010 条,丢弃前 1000000 条) -
✅ 延迟关联法:
sql
1SELECT t.* FROM table t 2INNER JOIN (SELECT id FROM table LIMIT 1000000, 10) tmp ON t.id = tmp.id;(先在索引树上查出 ID,再回表查数据,大幅减少扫描量)
-
✅ 游标法 (记录上次最大的 ID):
sql
sql1SELECT * FROM table WHERE id > 1000000 LIMIT 10;
- 小表驱动大表 :确保
JOIN时,被驱动表(右边的表)的连接字段上有索引。 - 字段类型一致:关联字段的数据类型和字符集必须完全一致,否则索引失效。
- 避免多表大连接:尽量控制在 3 张表以内。如果必须多表,考虑在应用层组装或冗余字段。
-
-
为什么嵌套查询会变慢?主表数据量大吗?
- 嵌套查询会变慢吗?
- 会 ,特别是相关子查询 ,它会导致 N×MN ×M 的复杂度。
- 非相关子查询在结果集过大时也会变慢。
- 主表数据量大吗?
- 如果主表数据量小,嵌套查询的影响可以忽略。
- 如果主表数据量大 (百万级以上),嵌套查询(尤其是相关子查询)是致命的,必须优化。
- 嵌套查询会变慢吗?
-
大部分性能问题是不是集中在回表或者关联条件没走索引导致全表扫描?
-
三、中间件与分布式 (RocketMQ/Redis)
-
消息队列 (RocketMQ):
-
项目中用到了RocketMQ吗?场景是什么?(订单生成到合同生成的解耦/削峰)
-
如果消息消费失败,如何保证事件的最终一致性?(重试机制、定时重推、人工排查)
-
场景题: 用户下单支付成功后发消息,随后取消订单也发消息,如果取消的消息先到了,如何保证业务不乱?(库存标记、状态检查、顺序控制)
-
-
分布式锁/Redis:
- 锁定库存时用的是哪种锁?(乐观锁/版本号)
- 有没有用过分布式锁?如果让你基于Redis写一个分布式锁,会考虑哪些方面?(看门狗机制防止死锁、锁的释放)
四、前端与运维 (Vue/Docker/Linux)
-
前端 (Vue):
- Vue中用过箭头函数吗?为什么要用箭头函数而不是普通函数?
- 箭头函数和普通函数在回调中最大的区别是什么?(
this指针指向问题)
-
运维与部署:
-
平时运维是分开还是开发兼任?
-
常用的Linux Shell命令有哪些?
-
使用Docker解决了服务部署的哪些问题?为什么之前部署慢?(环境一致性、镜像分发)
把代码 + 运行环境打包成了标准的镜像,共享宿主内核,轻量级。而以前是运行在虚拟机上,启动慢,占用大量内存
-
你们具体的部署流程是怎样的?(Jenkins打包 -> Tomcat部署)
创建dockerfile文件,构建镜像,推送到镜像仓库,docker运行。
运行环境分层共享,只读部分共享,写部分不共享
-
五、项目经验与业务场景
- 支付与库存:
- 支付完成和锁定库存的顺序是怎样的?为什么要先锁库存?(防止超卖)
- 如果用户一直不支付,如何超时取消并释放库存?(定时任务轮询 vs 延迟队列)
- 如果用户多次支付失败,业务上怎么处理?库存怎么释放?(日志排查、人工释放、线下处理)