面试题高优版

好的,已按要求删除 第二部分:JVM 内存调优(Q8、Q9)

更新后的面试题合集如下:


第一部分:并发编程(线程池 + 阻塞队列)

Q1:ArrayBlockingQueueLinkedBlockingQueue 有什么区别?各自适用什么场景?

参考答案

维度 ArrayBlockingQueue LinkedBlockingQueue
底层结构 数组(连续内存) 链表(节点分散)
锁机制 一把锁ReentrantLock 两把锁takeLock + putLock
存/取能否并行 ❌ 不能(同一把锁) ✅ 能(不同锁)
容量 必须指定,有界 可指定,默认 Integer.MAX_VALUE(无界)
性能(高并发) 较低 更高
适用场景 低并发、需要精确控制内存 高并发、多生产者多消费者

追问:为什么 LinkedBlockingQueue 性能更高?

因为存和取操作使用不同的锁,可以真正并行。同时使用 AtomicInteger 无锁地维护 count,避免了两把锁之间需要额外同步。


Q2:ConcurrentHashMap 如何实现线程安全的计数器?

参考答案

使用 ConcurrentHashMap 做计数器时,不能写成 map.put(key, map.get(key) + 1),因为 读-改-写 不是原子操作。

推荐写法

java 复制代码
// 方式一:使用 compute(原子操作)
counter.compute(key, (k, v) -> v == null ? 1 : v + 1);

// 方式二:使用 AtomicInteger 作为值(性能更高)
AtomicInteger ai = counter.computeIfAbsent(key, k -> new AtomicInteger(0));
ai.incrementAndGet();

追问:为什么 compute 是线程安全的?

ConcurrentHashMap.compute() 在操作期间,会锁住该 key 对应的桶(bucket),保证 读取旧值 → 执行计算 → 写入新值 整个过程是原子性的。


Q3:线程池的队列有哪些?如何影响线程池行为?

参考答案

队列 特点 对线程池的影响
LinkedBlockingQueue 有界/无界,吞吐量高 无界时 maxPoolSize 失效
ArrayBlockingQueue 有界,性能稳定 队列满后才创建非核心线程
SynchronousQueue 容量为0,直接交接 立即创建新线程,直到 maxPoolSize
PriorityBlockingQueue 按优先级出队 无界,任务按优先级执行
DelayQueue 延迟出队 用于定时/延迟任务

追问:生产环境为什么推荐使用有界队列?

无界队列可能导致任务无限堆积,最终 OOM。有界队列配合合理的拒绝策略,可以保护系统稳定性。


Q4:阻塞队列中 notEmptynotFull 是如何工作的?

参考答案

它们是 ArrayBlockingQueue 内部的两个 Condition 条件变量,配合 ReentrantLock 使用:

条件 关联角色 await() 时机 signal() 时机
notEmpty 消费者 队列为空时,消费者等待 生产者放入元素后,唤醒消费者
notFull 生产者 队列满时,生产者等待 消费者取出元素后,唤醒生产者

追问:为什么用 while 而不是 if 来检查条件?

防止虚假唤醒(spurious wakeup) ,线程从 await() 返回后必须重新检查条件是否满足。


Q5:LinkedBlockingQueue 中为什么在锁外执行 signalNotEmpty()

参考答案

put() 方法在入队后,如果发现旧 count == 0(队列之前为空),需要唤醒消费者。这个唤醒操作被设计在 putLock 外部执行:

java 复制代码
if (c == 0) {
    signalNotEmpty();  // 在 putLock 外面!
}

原因

  1. 缩短临界区 :减少 putLock 的持有时间,提升吞吐量。
  2. 避免锁传递开销signalNotEmpty() 需要获取 takeLock,如果在 putLock 内获取,会造成锁的交叉等待和上下文切换。
  3. 解耦存与取putLock 只管入队,takeLock 只管唤醒,职责清晰。

第二部分:数据库(InnoDB)

Q6:Redo LogBinlog 有什么区别?

维度 Redo Log Binlog
所属层 InnoDB 存储引擎 MySQL Server 层
日志内容 物理日志(页级修改) 逻辑日志(SQL 语句/行变更)
写入方式 循环写入,覆盖 追加写入,保留历史
用途 崩溃恢复(保证持久性) 主从复制、数据恢复

Q7:为什么需要 Redo Log?它解决了什么问题?

参考答案

Redo Log 解决的核心矛盾是:Buffer Pool 的高性能 vs 数据的持久性

  • 如果每次修改都直接写数据页 :16KB 数据页是随机 I/O,极慢(每次需要寻道)。
  • 如果只写 Buffer Pool:内存易失,宕机会丢失数据。

解决方案 :事务提交时,将修改操作以 顺序追加 的方式写入 Redo Log(顺序 I/O,极快),数据页异步刷盘。宕机后通过重放 Redo Log 恢复数据。

这就是 WAL(Write-Ahead Logging) 的核心思想:先写日志,再写数据


Q8:Buffer Pool 中的脏页什么时候刷到磁盘?

参考答案

脏页刷盘是异步 的,由后台线程触发,不随事务提交而刷盘

触发方式 说明
定时触发 page_cleaner 线程默认每秒执行一次
定量触发 脏页比例超过 innodb_max_dirty_pages_pct(默认 90%)
Redo Log 写满 需要推进 Checkpoint,强制刷盘(会阻塞所有用户线程
Buffer Pool 空间不足 LRU 淘汰时,脏页需先刷盘再淘汰
MySQL 正常关闭 所有脏页刷盘后才能退出
手动触发 FLUSH TABLES 等命令

关键结论事务提交只保证 Redo Log 落盘,不保证脏页落盘。


Q9:innodb_flush_log_at_trx_commit 三个值的区别?

行为 安全性 性能
1(默认) 每次提交,write() + fsync() ✅ 最高,不丢数据 ❌ 最差
2 每次提交,仅 write() 到 OS Cache,每秒 fsync() ⚠️ 宕机丢 1 秒数据 ✅ 较好
0 提交时不写,后台线程每秒 write() + fsync() ⚠️ 宕机丢 1 秒数据 ✅ 最好

核心write() 只是将数据从程序内存拷贝到 OS Cache,fsync() 才是真正落盘。


Q10:有了 Redo Log,MySQL 宕机还会丢数据吗?

参考答案

  • innodb_flush_log_at_trx_commit = 1(默认):不会丢任何已提交事务的数据。
  • = 2= 0:可能丢失最近 1 秒内已提交事务的数据。

所以答案是:默认情况下不会丢,但如果为了性能调整了参数,就可能丢。


Q11:Buffer PoolRedo Log BufferOS Cache 有什么区别?

参考答案

维度 Buffer Pool Redo Log Buffer OS Cache
位置 InnoDB 进程内存 InnoDB 进程内存 操作系统内核内存
存储内容 完整数据页(16KB) Redo Log 记录(512B 日志块) 文件页缓存
作用 读写缓存,避免磁盘 IO 事务持久化,顺序写入优化 所有文件 IO 的中转站
刷盘时机 异步(后台线程) 事务提交时触发 fsync() 或 OS 决定
数据安全性 易失(宕机丢失) 易失(需 fsync() 落盘) 易失(断电丢失)

第三部分:架构与设计

Q12:状态机(StateMachine)中"有状态"和"无状态"如何理解?

  • 有状态 :服务会 记住 之前的信息。例如 Spring StateMachine 的一个实例保存了当前状态,下次事件触发时不需要传入当前状态。
  • 无状态 :服务 记不住 ,每次请求必须带齐所有信息。例如 COLA 每次调用都需要传入 currentState,状态机本身只是一个查表的工具。

类比

  • 有状态 = 老主顾面馆:老板记住你的口味
  • 无状态 = 标准化面馆:每次来都要重新点单

Q13:规则引擎 Easy Rules 和表达式引擎 Aviator 的区别?

维度 Easy Rules Aviator
定位 轻量级规则引擎 高性能表达式引擎
解决问题 替代 if...else,管理独立规则 动态计算公式、表达式求值
动态性 弱(修改需重启) 强(表达式可存储外部,热更新)
性能 毫秒级 极高(编译为字节码)
适用场景 促销规则、数据校验 动态公式、风控评分

好的,已按要求删除 Q7 及之后的所有题目

更新后的面试题合集如下:


第三部分:数据库(InnoDB 进阶)

Q1:Undo Log 是什么?什么时候生成?

参考答案

Undo Log 是 InnoDB 存储引擎为实现事务的原子性(Atomicity)MVCC(多版本并发控制) 而维护的一份日志。

核心内容

  • 记录的是逻辑日志INSERT 对应一条 DELETEUPDATE 对应一条反向 UPDATE,用于回滚。
  • 存储位置:共享表空间(ibdata1)或独立的 Undo 表空间,持久化在磁盘上。

生成时机

  • INSERT 操作 :生成 TRX_UNDO_INSERT_REC,记录主键信息,用于回滚时删除。
  • UPDATE 操作 :生成 TRX_UNDO_UPD_EXIST_REC,记录修改前的旧版本数据。
  • DELETE 操作 :生成类似 UPDATE 的 Undo Log,标记删除并记录旧版本。

关键时机点

  • 在数据页被修改之前生成(先写日志,后写数据)。
  • 事务执行期间:每次 DML 操作都会立即生成对应的 Undo Log。
  • 事务提交时 :不会立即删除,需要保留到没有更早的事务需要看到这些旧版本 时,由后台 Purge 线程清理。

Q2:Undo LogMVCC 的关系是什么?

参考答案

Undo Log 是 MVCC 的数据基石

  • 版本链 :每一行数据被修改时,旧版本写入 Undo Log,行头隐藏字段 DB_ROLL_PTR 指向 Undo Log,形成版本链
  • Read View:事务查询时生成 Read View,记录活跃事务 ID 列表。
  • 可见性判断 :InnoDB 从当前版本开始,通过 DB_ROLL_PTR 沿版本链回溯,找到第一个符合可见性规则的版本。

#mermaid-svg-PZWkw7OIEHxrhmI4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PZWkw7OIEHxrhmI4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PZWkw7OIEHxrhmI4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PZWkw7OIEHxrhmI4 .error-icon{fill:#552222;}#mermaid-svg-PZWkw7OIEHxrhmI4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PZWkw7OIEHxrhmI4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PZWkw7OIEHxrhmI4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PZWkw7OIEHxrhmI4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PZWkw7OIEHxrhmI4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PZWkw7OIEHxrhmI4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PZWkw7OIEHxrhmI4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PZWkw7OIEHxrhmI4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PZWkw7OIEHxrhmI4 .marker.cross{stroke:#333333;}#mermaid-svg-PZWkw7OIEHxrhmI4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PZWkw7OIEHxrhmI4 p{margin:0;}#mermaid-svg-PZWkw7OIEHxrhmI4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PZWkw7OIEHxrhmI4 .cluster-label text{fill:#333;}#mermaid-svg-PZWkw7OIEHxrhmI4 .cluster-label span{color:#333;}#mermaid-svg-PZWkw7OIEHxrhmI4 .cluster-label span p{background-color:transparent;}#mermaid-svg-PZWkw7OIEHxrhmI4 .label text,#mermaid-svg-PZWkw7OIEHxrhmI4 span{fill:#333;color:#333;}#mermaid-svg-PZWkw7OIEHxrhmI4 .node rect,#mermaid-svg-PZWkw7OIEHxrhmI4 .node circle,#mermaid-svg-PZWkw7OIEHxrhmI4 .node ellipse,#mermaid-svg-PZWkw7OIEHxrhmI4 .node polygon,#mermaid-svg-PZWkw7OIEHxrhmI4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PZWkw7OIEHxrhmI4 .rough-node .label text,#mermaid-svg-PZWkw7OIEHxrhmI4 .node .label text,#mermaid-svg-PZWkw7OIEHxrhmI4 .image-shape .label,#mermaid-svg-PZWkw7OIEHxrhmI4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-PZWkw7OIEHxrhmI4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PZWkw7OIEHxrhmI4 .rough-node .label,#mermaid-svg-PZWkw7OIEHxrhmI4 .node .label,#mermaid-svg-PZWkw7OIEHxrhmI4 .image-shape .label,#mermaid-svg-PZWkw7OIEHxrhmI4 .icon-shape .label{text-align:center;}#mermaid-svg-PZWkw7OIEHxrhmI4 .node.clickable{cursor:pointer;}#mermaid-svg-PZWkw7OIEHxrhmI4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PZWkw7OIEHxrhmI4 .arrowheadPath{fill:#333333;}#mermaid-svg-PZWkw7OIEHxrhmI4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PZWkw7OIEHxrhmI4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PZWkw7OIEHxrhmI4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PZWkw7OIEHxrhmI4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PZWkw7OIEHxrhmI4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PZWkw7OIEHxrhmI4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PZWkw7OIEHxrhmI4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PZWkw7OIEHxrhmI4 .cluster text{fill:#333;}#mermaid-svg-PZWkw7OIEHxrhmI4 .cluster span{color:#333;}#mermaid-svg-PZWkw7OIEHxrhmI4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PZWkw7OIEHxrhmI4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PZWkw7OIEHxrhmI4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-PZWkw7OIEHxrhmI4 .icon-shape,#mermaid-svg-PZWkw7OIEHxrhmI4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PZWkw7OIEHxrhmI4 .icon-shape p,#mermaid-svg-PZWkw7OIEHxrhmI4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PZWkw7OIEHxrhmI4 .icon-shape .label rect,#mermaid-svg-PZWkw7OIEHxrhmI4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PZWkw7OIEHxrhmI4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PZWkw7OIEHxrhmI4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PZWkw7OIEHxrhmI4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} DB_ROLL_PTR
DB_ROLL_PTR
当前数据行

DB_TRX_ID=101
Undo Log

版本1

DB_TRX_ID=100
Undo Log

版本0

DB_TRX_ID=99

一句话Undo Log 存数据,Read View 定规则,两者结合实现 MVCC。


Q3:什么是"快照读"和"当前读"?举例说明。

参考答案

维度 快照读(Snapshot Read) 当前读(Current Read)
读取版本 历史版本(事务开始时或 Read View 生成时) 最新已提交版本
是否加锁 不加锁 加锁(共享锁或排他锁)
典型操作 普通 SELECT SELECT ... FOR UPDATEUPDATEDELETEINSERT

举例

sql 复制代码
-- 事务 A
START TRANSACTION;
SELECT balance FROM account WHERE id = 1;  -- ① 快照读: 1000

-- 事务 B 修改并提交
UPDATE account SET balance = 800 WHERE id = 1; COMMIT;

-- 事务 A 再次查询
SELECT balance FROM account WHERE id = 1;          -- ② 快照读: 1000(可重复读)
SELECT balance FROM account WHERE id = 1 FOR UPDATE; -- ③ 当前读: 800

Q4:快照读和当前读分别适用于什么场景?

参考答案

场景 推荐读取方式 理由
报表统计、数据分析 快照读 数据量大,避免加锁阻塞
商品/文章详情页 快照读 读多写少,高并发下性能优先
库存扣减、余额扣减 当前读FOR UPDATE 必须读取最新数据,防止超卖
执行 UPDATE / DELETE 当前读(自动) 必须基于最新数据版本操作
防重复提交、唯一性校验 当前读LOCK IN SHARE MODE 避免并发场景下的重复写入

Q5:Binlog 是什么?有几种格式?

参考答案

Binlog(Binary Log) 是 MySQL Server 层的二进制日志,记录所有对数据库产生变更的操作。

核心用途

  1. 主从复制:主库写入 Binlog,从库读取并重放。
  2. 数据恢复:配合全量备份,实现时间点恢复(Point-in-Time Recovery)。
  3. 数据审计:分析变更操作,排查安全问题。

三种格式

格式 记录方式 优点 缺点 适用场景
STATEMENT 记录 SQL 语句 日志量小,节省 I/O 非确定性函数可能导致主从不一致 数据一致性要求不高的场景
ROW 记录每行数据的变更 数据一致性高(MySQL 8.0+ 默认) 日志量可能较大 金融、交易等强一致性场景
MIXED MySQL 自动切换 兼顾性能与一致性 行为有一定复杂性 通用业务场景,稳妥选择

Q6:什么是 MySQL 的二阶段提交(2PC)?解决了什么问题?

参考答案

MySQL 的二阶段提交是 InnoDB 为了协调 Redo LogBinlog 两份日志之间的一致性,而引入的内部机制。

解决的问题:Redo Log 写了但 Binlog 没写(或反过来),导致主从不一致。

流程

复制代码
阶段一(Prepare):
  ① 执行 SQL,在 Buffer Pool 中修改数据页
  ② 写入 Redo Log,状态标记为 prepare
  ③ 写入 Binlog

阶段二(Commit):
  ④ Binlog 写入成功后,将 Redo Log 状态更新为 commit
  ⑤ 根据参数刷盘,返回"提交成功"

崩溃恢复判断逻辑

Redo Log 状态 Binlog 状态 恢复决策
prepare 不存在 回滚
prepare 存在且完整 提交
commit 存在且完整 提交

💡 总结

以上涵盖了你问过的所有数据库知识点,按 Undo Log → MVCC → 当前读/快照读 → Binlog → 二阶段提交 递进组织。

如果还想补充某个问题的追问细节(比如"二阶段提交如何判断 Binlog 是否完整"、"Purge 线程的工作机制"等),随时告诉我。😊

相关推荐
HONG````7 小时前
HarmonyOS ArkUI Badge 徽标与 Chip 标签:消息红点、数字徽标与可选中标签
后端
卷无止境7 小时前
Python itertools 盘点真正用得上的20个常用方法
后端
芒鸽7 小时前
HarmonyOS ArkUI Marquee 跑马灯与 Gauge 仪表盘:公告滚动与数据展示
后端
Gopher_HBo7 小时前
镜像服务门面模式
后端
一缕清烟在人间7 小时前
CalendarYearPage 年视图:12 个月微型日历网格
后端
xianjixiance_7 小时前
月份切换与年份导航:prevMonth / nextMonth 逻辑
后端
花开彼岸天~8 小时前
有日记日期标记:小圆点 + 加粗字重 + 点击跳转
后端
AskHarries8 小时前
Google 登录配置
后端
全堆鸿蒙8 小时前
时间轴组件:Circle 节点 + 竖线连接 + 内容列表
后端