# 派安盈一面复盘（2026-06-03）

## 这轮面试在考什么

这轮派安盈一面的题目明显分成两块：

1. 支付资金域：换汇、余额、虚拟账户、重复支付防重。
2. Java 和数据库基础：OOM、锁、ThreadLocal、MVCC、慢 SQL、事务、海量数据治理。

面试官真正想看的不是你会不会背知识点，而是：

- 你是否理解资金在系统里的真实流转。
- 你是否能讲清楚支付系统里的一致性控制和防重。
- 你是否有比较扎实的并发和数据库基础。

---

## 一、支付资金域

### 1. 你们项目换汇有没有限时锁汇

#### 建议口径

比较成熟的多币种系统通常会有“报价有效期”或者“限时锁汇”机制。

原因是汇率是波动的，不能让用户拿着一个很早的报价无限期成交。常见做法是生成一条带过期时间的 quote，里面包含：

- 源币种
- 目标币种
- 汇率
- 手续费
- 有效期

用户在有效期内确认，就按该报价执行；过期则重新报价。

#### 项目结合点

如果当前项目没有做成完整 lock rate，可以诚实说当前更偏“交易时取汇率 / 入账时换算”，但从产品设计角度，限时锁汇更适合面向客户的换汇能力。

---

### 2. 有没有了解过虚拟账户

#### 建议口径

虚拟账户本质上是“系统中的收款标识”，不一定对应一张真实银行账户。

它的核心价值在于：

- 识别入金归属
- 支持按客户、商户、订单维度分配收款标识
- 让系统自动归集资金

#### 项目结合点

在多币种收款账户场景里，虚拟账户特别适合做：

- 本地收款
- 入金归属识别
- 对账匹配
- 资金归集

---

### 3. 如果换汇的时候余额不够，你们怎么做

#### 建议口径

换汇前必须先校验余额，但要分清楚是哪种“不够”：

- 总余额不够：直接拒绝换汇。
- 可用余额不够但总余额够：说明有冻结资金，当前不能使用。
- 单币种余额不够：如果支持自动购汇补足则走补足，否则失败。

#### 项目结合点

在项目里更稳妥的做法是：

1. 先校验 source currency 的 `available balance`
2. 成功后冻结或预扣源币种余额
3. 再执行换汇
4. 成功则减少源币种，增加目标币种
5. 失败则回滚冻结

---

### 4. 换汇余额这块是怎么变动的

#### 建议口径

换汇本质上是一次双边账务处理：

- 源币种余额减少
- 目标币种余额增加
- 中间可能还有手续费或汇差

一个标准流程是：

1. 校验源币种可用余额
2. 冻结或预扣源币种金额
3. 根据汇率算目标币种应入账金额
4. 扣减源币种余额
5. 增加目标币种余额
6. 手续费单独记账
7. 全程记录账务流水

#### 面试表达

不要说成“改两个余额字段”，而要说成“标准账务处理，有冻结、有流水、有失败回滚”。

---

### 5. 怎么防止一笔订单重复支付

#### 建议口径

防重支付不能只靠前端，后端至少做四层：

1. 业务单号唯一
2. 幂等键控制
3. 状态机校验
4. 数据库唯一约束或 Redis 锁

#### 项目结合点

真正稳的是：

- `merchantId + merchantOrderNo + actionType` 幂等
- 订单状态机控制
- 数据库唯一索引防并发重复落单
- Redis 锁作为第一层快互斥

---

## 二、Java 并发和内存

### 6. 线上出现 OOM 异常，排查思路是什么

#### 建议口径

我会先分类型，再看现场。

先确认是：

- 堆内存 OOM
- Metaspace OOM
- 直接内存 OOM
- 线程过多带来的内存问题

然后按三步走：

1. 看监控：内存曲线、GC、线程数、流量
2. 看现场：heap dump、MAT、Arthas
3. 看根因：缓存、积压、批任务、对象没释放、线程泄漏

#### 项目结合点

支付系统里常见根因往往是：

- 大批量对账
- 导出报表
- MQ 消息积压
- 本地缓存失控

---

### 7. syn 和 lock 这块讲一下

#### 建议口径

`synchronized` 是 JVM 内置锁，语法简单、自动释放，适合简单同步场景。

`Lock` 是 JUC 提供的显式锁，像 `ReentrantLock`，优势是：

- 可中断
- 可超时
- 支持公平锁
- 支持条件队列
- 手动控制加解锁

#### 面试表达

简单同步我优先用 `synchronized`，需要更灵活控制时用 `Lock`。

---

### 8. ThreadLocal 讲一下，以及会出现什么问题

#### 建议口径

ThreadLocal 是给每个线程保存独立副本，不是加锁。

常见用途：

- 用户上下文
- TraceId
- 数据源路由上下文

#### 典型问题

- 线程池复用导致脏数据串线程
- 不清理会有内存泄漏风险
- 异步线程切换后上下文丢失

#### 面试表达

重点强调在线程池场景下一定要 `remove()`。

---

### 9. HashMap 和 ConcurrentHashMap 区别

#### 建议口径

- HashMap 非线程安全
- ConcurrentHashMap 线程安全
- JDK 8 后 ConcurrentHashMap 主要基于 CAS + synchronized 提高并发能力

#### 项目结合点

共享配置缓存、规则缓存这类多线程访问场景，不能随便用 HashMap。

---

## 三、数据库和底层原理

### 10. MVCC 实现原理讲一下

#### 建议口径

MVCC 的核心是让快照读不加锁也能读到一致性版本。

InnoDB 主要依赖：

- 行记录隐藏字段
- undo log 版本链
- Read View

普通快照读会沿着 undo log 找到当前事务可见的那个版本。

---

### 11. 怎么排查慢 SQL 问题

#### 建议口径

我会按四层排查：

1. 先从慢 SQL 日志或监控定位 SQL
2. 用 `EXPLAIN` 看执行计划
3. 分析是索引问题、where 条件问题、join 太重还是分页问题
4. 再决定补索引、改 SQL、拆查询、冷热分离还是分表

---

### 12. 数据库事务这块讲一下

#### 建议口径

事务核心是 ACID。

面试里更重要的是能讲清：

- 隔离级别
- InnoDB 默认可重复读
- 本地事务解决单服务内一致性
- 跨服务一致性不能单靠数据库事务

#### 项目结合点

支付系统里跨服务一致性通常靠 MQ、补偿、幂等，而不是指望数据库事务包住一切。

---

### 13. 数据库如果表出现了上亿数据行，查询很慢，怎么办

#### 建议口径

不要一上来就说分库分表，而是按层治理：

1. 先看索引
2. 优化 SQL
3. 冷热分离
4. 历史归档
5. 高并发查询做汇总表或搜索索引
6. 最后再考虑水平分表

---

### 14. 如果发现做了冷热库还是很慢，还有没有别的方案

#### 建议口径

有，说明问题不只是冷热，而是查询架构要调整。

可以继续做：

- 汇总表 / 宽表
- ES / ClickHouse / OLAP
- 游标翻页替代 offset 分页
- 异步报表或离线计算

#### 面试表达

冷热分离解决的是存储层问题，不一定能彻底解决复杂查询问题。查询架构本身也要改。

---

## 四、这轮你应该怎么讲得更像做过项目

建议顺序：

1. 先从换汇、余额、重复支付防重讲支付资金域
2. 再补技术层兜底：幂等、状态机、唯一约束、冻结余额、资金流水
3. 最后补底层基础：OOM、锁、ThreadLocal、MVCC、慢 SQL、事务

这样会更像你真的做过资金系统，而不是只刷了题，也更符合派安盈这类跨境支付公司的一面预期。

---

## 五、最值得补 demo 的点

1. 换汇 demo：冻结源币种、成功换汇、失败回滚、手续费流水
2. 重复支付防重 demo：幂等键 + 状态机 + 数据库唯一约束
3. ThreadLocal demo：线程池复用导致脏数据，再用 `remove()` 修复
4. 慢 SQL demo：对比不同执行计划和分页方式
