NO 1. spring的事物,事物失效的场景
Spring 事务
核心概念
Spring 事务本质是 对数据库连接的提交/回滚做统一管理,常用 @Transactional 声明式事务,底层靠 AOP 动态代理 实现。
调用方 → 代理对象 → 开启事务 → 执行业务 → 提交/回滚
传播行为(Propagation)
| 传播行为 | 含义 |
|---|---|
| REQUIRED(默认) | 有事务就加入,没有就新建 |
| REQUIRES_NEW | 总是新建事务,挂起当前事务 |
| NESTED | 嵌套事务,外层回滚会带动内层 |
| SUPPORTS | 有事务就加入,没有就非事务执行 |
| NOT_SUPPORTED | 以非事务执行,挂起当前事务 |
| MANDATORY | 必须在事务中,否则抛异常 |
| NEVER | 不能在事务中,否则抛异常 |
隔离级别(Isolation)
对应数据库隔离级别:DEFAULT、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE。
回滚规则
-
默认:RuntimeException 和 Error 回滚
-
Checked Exception 默认 不回滚
-
可配置:
rollbackFor = Exception.class、noRollbackFor = ...@Transactional(rollbackFor = Exception.class)
public void transfer() throws Exception {
// ...
}
事务失效的常见场景(面试重点)
1. 方法不是 public
Spring AOP 默认只代理 public 方法,private/protected 事务不生效。
@Transactional
private void update() { } // 失效
2. 同类内部自调用(最常见)
代理只拦 外部调用,类内部 this.method() 不走代理。
@Service
public class OrderService {
public void createOrder() {
this.saveOrder(); // 失效,没走代理
}
@Transactional
public void saveOrder() { }
}
解决:
- 注入自身(
@Lazy @Autowired private OrderService self) - 拆到另一个 Service
- 使用
AopContext.currentProxy()
3. 异常被吞掉或 catch 后没再抛出
@Transactional
public void update() {
try {
// ...
} catch (Exception e) {
log.error("error", e); // 吞掉异常,事务不会回滚
}
}
解决: catch 后 throw e,或 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
4. 抛出的异常类型不对
默认 Checked Exception 不回滚:
@Transactional
public void update() throws Exception {
throw new Exception("业务异常"); // 默认不回滚
}
解决: rollbackFor = Exception.class
5. 数据库引擎不支持事务
如 MySQL 用 MyISAM 不支持事务,要用 InnoDB。
6. 多数据源时数据源或事务管理器配置错误
@Transactional 没指定 transactionManager,或动态数据源路由错误,可能导致事务绑错连接。
7. 没有被 Spring 管理
类没加 @Service / @Component,或 new 出来的对象,代理不会生效。
OrderService service = new OrderService(); // 失效
8. 传播行为设置不当
例如:
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void method() { } // 故意非事务执行
或外层 REQUIRES_NEW、内层逻辑理解错误,导致"以为有事务、实际没有"。
9. 只读事务误用
@Transactional(readOnly = true)
public void update() { } // 可能只读优化,写操作异常或行为不符合预期
10. 异步执行导致事务上下文丢失
@Async、新线程、CompletableFuture 里执行数据库操作,新线程拿不到原线程事务。
@Transactional
public void process() {
executor.execute(() -> {
dao.update(); // 不在同一事务里
});
}
11. 分布式场景误以为是本地事务
跨服务、跨库调用,本地 @Transactional 管不了远程,需要 Seata、消息最终一致性等。
如何排查事务是否生效
- 日志开
org.springframework.transactionDEBUG - 看是否有
Creating new transaction/Committing/Rolling back - 打断点看调用的是 代理对象 还是 原始对象
- 故意抛 RuntimeException,看数据是否回滚
面试口述模板(30 秒版)
NOSpring 事务通过 AOP 代理实现,
@Transactional在方法前后做开启、提交或回滚。失效最常见三类:同类自调用不走代理、异常被 catch 没抛出、抛了 Checked Exception 默认不回滚。
另外还有方法非 public、Bean 没被 Spring 管理、MyISAM 不支持事务、异步线程丢失事务上下文等。
实际项目里我一般避免同类自调用,统一
rollbackFor = Exception.class,关键业务用集成测试验证回滚。
NO2 . 老项目没日志,怎么让它有日志
分层次、渐进式改造:
方案一:引入日志框架(最小改动)
- 加依赖:
logback或log4j2(Spring Boot 默认 logback) - 配置
logback-spring.xml:控制台 + 滚动文件、按天归档 - 全局替换
System.out.println→log.info/debug/error - 用 IDE 批量重构 + Code Review
方案二:AOP 统一打日志(侵入小)
@Aspect
@Component
public class LogAspect {
@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public Object log(ProceedingJoinPoint pjp) throws Throwable {
log.info("请求: {} 参数: {}", pjp.getSignature(), Arrays.toString(pjp.getArgs()));
long start = System.currentTimeMillis();
try {
Object result = pjp.proceed();
log.info("耗时: {}ms", System.currentTimeMillis() - start);
return result;
} catch (Exception e) {
log.error("异常", e);
throw e;
}
}
}
方案三:过滤器 / 拦截器
- Servlet
Filter或 SpringHandlerInterceptor记录 HTTP 请求 URL、IP、耗时
方案四:网关 / 代理层
- 在 Nginx、API Gateway 记录访问日志,业务代码零改动
方案五:链路追踪
- 接入 SkyWalking、Zipkin,自动采集调用链
实施建议
| 优先级 | 内容 |
|---|---|
| P0 | 异常必须 log.error + 堆栈 |
| P1 | 关键业务节点(下单、支付、对账) |
| P2 | 入参出参、SQL 慢查询 |
| P3 | 全链路 traceId(MDC) |
企业场景可提:
- 日志集中收集(ELK、Loki)
- 敏感信息脱敏(证件号、手机号)
- 与现有运维监控平台对接
NO3 . 业务Redis有一个大Value,如何不影响业务正常使用,把它删掉?
生产删 Redis 大 Value,核心是 避免 DEL 阻塞单线程。
首选 UNLINK 异步删除;若 Key 仍在使用,先 业务切到新 Key 或做双写,确认无流量后再删。
Hash/Set 等超大集合用 SCAN 分批删除。
操作选低峰、开监控、集群注意分片,必要时先备份。
根本治理是 避免大 Value:控制缓存粒度、压缩、拆分 Key、设置合理 TTL
NO4 . 做项目如何正确的做技术选型
设计流程(结构化回答):
需求分析 → 功能、非功能(QPS、可用性、安全)
领域建模 → 模块划分、边界
架构选型 → 单体 / 微服务 / 分层
技术选型 → 语言、框架、DB、中间件
接口设计 → REST/API 规范、文档
数据设计 → ER、索引、分库分表
非功能 → 日志、监控、部署、灾备
迭代计划 → MVP、里程碑
技术选型考虑因素:
| 因素 | 示例 |
|---|---|
| 团队熟悉度 | 东航 Oracle 存量多,可延续 Oracle 或信创达梦 |
| 业务规模 | 小项目单体 Spring Boot + MySQL 够用 |
| 性能要求 | 高并发加 Redis、消息队列、读写分离 |
| 合规 | 信创、等保、数据出境 |
| 运维成本 | 云原生 vs 传统机房 |
| 生态 | Spring 生态成熟,文档和社区好 |
示例:
Spring Boot + Spring Cloud + Oracle/达梦 + Redis + Kafka + Nacos + Gateway;
日志 ELK,监控 Prometheus;容器 K8s 部署
NO5. Spring IOC 和 AOP 原理
IOC(控制反转)
- 传统:对象自己
new依赖 - IOC:容器负责创建对象、注入依赖(依赖注入 DI)
原理:
- 扫描
@Component、@Service等 - 解析 Bean 定义(BeanDefinition)
- 通过反射创建 Bean,处理
@Autowired/@Resource注入 - 单例默认缓存在
Singleton容器
相关点: @Configuration、@Bean、@Scope(singleton/prototype)、循环依赖(三级缓存)、ApplicationContext vs BeanFactory
AOP(面向切面编程)
- 目的:把日志、事务、权限、性能监控等横切逻辑从业务代码中剥离
原理:
- 动态代理:接口用 JDK 动态代理;无接口用 CGLIB 子类代理
- 核心概念:切点(Pointcut)、通知(Advice:Before/After/Around)、切面(Aspect)、连接点(JoinPoint)
常见应用:
@Transactional事务管理@Cacheable缓存- 自定义
@Log注解记录操作日志
口述示例:
Spring 在 Bean 初始化后,对需要代理的 Bean 包装一层代理对象;调用方法时先走切面逻辑,再调用目标方法。
NO6 . 微服务架构
定义: 将单体应用拆成多个独立部署、独立扩展、松耦合的小服务,每个服务负责单一业务能力。
核心要点:
客户端 → API 网关 → 微服务集群
↓
注册中心(Nacos/Eureka)
↓
配置中心、链路追踪、熔断限流
| 维度 | 常见技术 |
|---|---|
| 通信 | REST、Feign、gRPC、消息队列(Kafka/RocketMQ) |
| 注册发现 | Nacos、Eureka、Consul |
| 网关 | Spring Cloud Gateway、Kong |
| 配置 | Nacos Config、Apollo |
| 熔断限流 | Sentinel、Hystrix |
| 分布式事务 | Seata(AT/TCC/Saga)、最终一致性 + 消息 |
| 链路追踪 | SkyWalking、Zipkin |
| 容器化 | Docker + K8s |
优缺点(要能讲):
- 优点:独立部署、技术栈灵活、故障隔离、团队自治
- 缺点:分布式复杂度、运维成本高、数据一致性难、调用链变长
核心系统与外围系统解耦;与 Oracle 存量系统通过 API/消息集成;信创环境下中间件国产化适配。
NO7. Oracle 存储过程及相关技术点
存储过程(Procedure):预编译的 PL/SQL 块,封装业务逻辑,减少网络往返,便于权限控制和复用。
相关技术点:
| 技术 | 作用 |
|---|---|
| PL/SQL | 过程化扩展:变量、游标、异常处理、循环 |
| Package(包) | 规范 + 体,封装过程/函数,支持重载、私有成员 |
| Function | 有返回值,可在 SQL 中调用 |
| Trigger | 行级/语句级触发器,审计、同步、校验 |
| Cursor | 显式/隐式游标,批量处理 |
| Exception | WHEN OTHERS、RAISE_APPLICATION_ERROR |
| 事务控制 | COMMIT/ROLLBACK,注意自治事务 PRAGMA AUTONOMOUS_TRANSACTION |
| 动态 SQL | EXECUTE IMMEDIATE、DBMS_SQL |
| DBMS_JOB / DBMS_SCHEDULER | 定时任务 |
| 性能 | 绑定变量、避免硬解析;批量 FORALL/BULK COLLECT |
扩展问题:擅长的数据库?
我主要熟悉 Oracle,日常涉及表设计、SQL 调优、索引、执行计划、存储过程/包、分区表、物化视图等。对 SQL Server / MySQL 也有一定使用经验。
若项目以 Oracle 为主,我会重点关注:高可用(RAC/Data Guard)、大批量数据处理、存储过程维护、性能与锁问题排查。
NO8. 大表查询优化思路(Oracle & MySQL 通用 + 差异)
大表查询慢,通常卡在:全表扫描、回表多、排序/分组重、锁等待、网络传输大。优化顺序建议:
先确认慢在哪 → 再改 SQL/索引 → 再改表结构 → 最后分库分表/归档
一、优化前必做:定位瓶颈
1. 看执行计划(最重要)
| 数据库 | 命令 |
|---|---|
| Oracle | EXPLAIN PLAN FOR ... + DBMS_XPLAN.DISPLAY;或 DBMS_SQLTUNE |
| MySQL | EXPLAIN / EXPLAIN ANALYZE(8.0.18+) |
关注:
- 访问方式:Full Table Scan vs Index Range Scan vs Index Skip Scan
- Rows / Cardinality:预估行数是否离谱(统计信息过期)
- Extra / Operation:Using filesort、Using temporary、TABLE ACCESS FULL
- Cost / Actual Time:哪一步最耗时
2. 看 SQL 本身
- 是否
SELECT * - 是否对索引列做函数/运算:
WHERE YEAR(create_time)=2024 - 是否
%xxx%左模糊 - 是否隐式类型转换导致索引失效
- 是否 OR、NOT IN、<> 导致优化器放弃索引
- 是否深分页:
LIMIT 1000000, 20
二、通用优化手段(Oracle & MySQL 都适用)
1. 索引优化
原则:让查询走"索引范围扫描 + 尽量少回表"。
-- 差:函数导致索引失效
WHERE TO_CHAR(create_time, 'YYYY-MM') = '2024-06'
-- 好:范围查询
WHERE create_time >= TO_DATE('2024-06-01')
AND create_time < TO_DATE('2024-07-01')
联合索引(最左前缀):
-- 查询:WHERE user_id = ? AND status = ? ORDER BY create_time
-- 索引:(user_id, status, create_time)
覆盖索引(减少回表):
-- 只查索引里有的列
SELECT user_id, status, create_time
FROM orders
WHERE user_id = 100 AND status = 1;
2. 减少返回数据量
-
只查需要的列,避免
SELECT * -
加分页 / 限制条数
-
深分页改 游标分页(基于上次最大 ID):
-- MySQL 差
LIMIT 1000000, 20
-- MySQL 好
WHERE id > 1000000 ORDER BY id LIMIT 20
-- Oracle 好
WHERE id > :last_id AND ROWNUM <= 20 -- 或 OFFSET/FETCH 配合索引
3. SQL 改写
| 场景 | 优化 |
|---|---|
OR 多条件 |
改 UNION ALL 分别走索引 |
NOT IN |
改 NOT EXISTS 或 LEFT JOIN ... IS NULL |
| 大 IN 列表 | 拆批、临时表、JOIN |
| 子查询 | 改 JOIN(视执行计划而定) |
| 先排序再过滤 | 调整 WHERE 顺序,让优化器先过滤 |
4. 统计信息及时更新
优化器靠统计信息选计划,大表变更后计划可能变差。
| 数据库 | 做法 |
|---|---|
| Oracle | DBMS_STATS.GATHER_TABLE_STATS |
| MySQL | ANALYZE TABLE t |
5. 表结构层面
- 合适的数据类型(避免过大字段拖慢扫描)
- 大字段(TEXT/BLOB/CLOB)拆到附表,主表只留常用列
- 历史数据 归档/分区,查询只扫热数据
6. 架构层面
- 读写分离:报表走从库
- 汇总表 / 物化视图 / 定时任务预聚合
- 搜索引擎(ES)做复杂检索
- 分库分表(真正亿级且单表优化到头)
三、Oracle 大表优化特有手段
1. 分区表(Partitioning)
按时间、地区等分区,查询带分区键时 Partition Pruning,只扫相关分区。
-- 按月分区,查 6 月只扫 6 月分区
WHERE order_date >= DATE '2024-06-01'
AND order_date < DATE '2024-07-01'
常见:Range(时间)、List(地区)、Hash(均匀打散)。
2. 并行查询(Parallel Query)
大表全表扫描、大聚合可开并行(适合报表,OLTP 慎用):
SELECT /*+ PARALLEL(t, 4) */ COUNT(*) FROM big_table t;
3. 物化视图(Materialized View)
复杂统计预计算,定时刷新:
CREATE MATERIALIZED VIEW mv_daily_sales
REFRESH FAST ON COMMIT -- 或 ON DEMAND
AS
SELECT trunc(order_date), region, SUM(amount)
FROM orders
GROUP BY trunc(order_date), region;
4. 索引类型
| 类型 | 场景 |
|---|---|
| B-Tree | 常规范围/等值 |
| Bitmap Index | 低基数列(性别、状态),DW 场景 |
| Function-Based Index | 对表达式建索引 |
| Index Organized Table (IOT) | 主键查询为主的大表 |
5. HINT 干预(谨慎)
SELECT /*+ INDEX(t idx_order_date) */ ...
6. AWR / ASH 分析
用 AWR 报告看 Top SQL、等待事件,定位全表扫描、direct path read 等。
四、MySQL 大表优化特有手段
1. 分区表(MySQL 5.7+ / 8.0)
类似 Oracle,按 RANGE/LIST/HASH/KEY 分区,注意:
- 分区键必须在 WHERE 里才能剪枝
- 单表分区数不宜过多
- 很多场景 归档拆表 比分区更简单
2. InnoDB 引擎要点
- 必须用 InnoDB(支持事务、行锁、聚簇索引)
- 主键尽量短且递增(避免 UUID 随机主键导致页分裂)
- 二级索引叶子节点存主键,回表成本高 → 覆盖索引更重要
3. 索引下推(ICP,5.6+)
联合索引中,存储引擎层先过滤,减少回表:
Extra: Using index condition
4. MRR(Multi-Range Read,5.6+)
优化 WHERE col IN (...) 的回表顺序,减少随机 IO。
5. 索引合并 / Skip Scan
优化器可能合并多个索引;8.0 对某些场景有 Index Skip Scan。
6. 执行计划管理
- Optimizer Hints:
/*+ INDEX(t idx_xx) */ - 8.0 Index Statistics:直方图
ANALYZE TABLE生成
7. 大表 DDL 注意
- 加索引用 Online DDL(
ALGORITHM=INPLACE, LOCK=NONE)或 pt-osc/gh-ost - 避免高峰期
ALTER TABLE锁表
五、典型场景对照
场景 1:按时间查最近 7 天订单(亿级)
✓ 时间字段建索引或分区(按月/按天)
✓ WHERE create_time >= ? AND create_time < ?
✓ 只查必要列
✓ 历史数据归档到历史表
场景 2:深分页列表
✗ LIMIT 500000, 20
✓ WHERE id > :lastId ORDER BY id LIMIT 20
✓ 或搜索引擎 / 游标方案
场景 3:多条件 + 排序
✓ 联合索引 (status, create_time) 或 (user_id, create_time)
✓ 避免 filesort:让 ORDER BY 列在索引里且顺序一致
场景 4:COUNT(*) 大表
✗ 实时 COUNT 全表
✓ 汇总表 / 缓存计数
✓ 分区分别 COUNT 再汇总
✓ 近似值(Redis、表统计行数仅参考)
场景 4:报表类复杂聚合
✓ 离线/定时预聚合
✓ Oracle 物化视图 / MySQL 汇总表
✓ 走从库,必要时 Oracle Parallel
六、Oracle vs MySQL 快速对比
| 维度 | Oracle | MySQL |
|---|---|---|
| 执行计划 | DBMS_XPLAN、AWR | EXPLAIN / EXPLAIN ANALYZE |
| 分区 | 成熟,企业常用 | 有,但用得相对少 |
| 预计算 | 物化视图很强 | 主要靠汇总表/定时任务 |
| 并行查询 | Parallel Query 成熟 | 8.0 有限支持 |
| 索引类型 | B-Tree、Bitmap、函数索引 | 主要是 B-Tree(8.0 有函数索引) |
| 优化器 | CBO 很强,统计信息关键 | 8.0 优化器明显改善 |
| 大表 DDL | 在线重定义等工具多 | Online DDL / pt-osc |
七、面试 1 分钟口述版
大表查询优化我先看 执行计划,确认是全表扫描、回表多还是排序临时表。
然后 改 SQL:避免
SELECT *、函数破坏索引、深分页;建立合适的 联合索引/覆盖索引;必要时 改写 OR、NOT IN。表层面做 分区、归档、冷热分离;统计信息要 及时更新。
报表类用 汇总表/物化视图,亿级再考虑 分库分表。
Oracle 侧重 分区 + 物化视图 + Parallel;MySQL 侧重 InnoDB 索引设计、覆盖索引、ICP、游标分页。