1.什么是事务
2.如何使用事务
3.在使用事务的时候会出现哪些特殊的场景
4.事务失效的常见场景
5.事务实现的原理是什么
1. 什么是事务
事务(Transaction)是数据库管理系统 执行过程中的一个逻辑单元,它由一组操作(通常是一组SQL语句)组成,这些操作要么全部成功 ,要么全部失败(即全部不执行)。
事务是为了保证数据的一致性和完整性而提出的概念。最经典的例子就是银行转账:从A账户扣款和向B账户加款这两个操作必须作为一个整体,要么都成功,要么都失败,不能出现一边扣了另一边没加的情况。
事务的四大特性(ACID):
- 原子性(Atomicity):事务中的所有操作要么全部提交成功,要么全部失败回滚。
- 一致性(Consistency):事务执行前后,数据从一个一致状态变到另一个一致状态(比如转账前后总金额不变)。
- 隔离性(Isolation):并发执行的事务之间互相不干扰,一个事务的中间状态对其他事务不可见。
- 持久性(Durability):事务一旦提交,对数据的改变就是永久性的,即使系统崩溃也不会丢失。
2. 如何使用事务
在不同的技术栈中使用方式不同,这里以最常用的 MySQL + Spring(Java)为例。
2.1 在MySQL中直接使用
sql
-- 开始事务
START TRANSACTION; 或 BEGIN;
-- 执行一组SQL
UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;
-- 如果都成功,提交
COMMIT;
-- 如果出错,回滚
ROLLBACK;
2.2 在Spring Boot中使用(声明式事务,最常用)
java
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// 在方法上添加@Transactional注解即可
@Transactional
public void transfer(Long fromId, Long toId, int amount) {
userMapper.decreaseBalance(fromId, amount);
// 如果这里抛出异常,上面扣款操作会自动回滚
userMapper.increaseBalance(toId, amount);
}
}
2.3 编程式事务(更灵活,但代码侵入性强)
java
@Autowired
private TransactionTemplate transactionTemplate;
public void doSomething() {
transactionTemplate.execute(status -> {
// 业务代码
return result;
});
}
关键点 :实际开发中99%的场景使用 @Transactional 即可,需要开启事务管理器配置(Spring Boot自动配置)。
3. 使用事务时会出现哪些特殊场景
| 特殊场景 | 说明 | 示例/解决方案 |
|---|---|---|
| 事务传播行为 | 一个事务方法调用另一个事务方法,事务如何传递? | REQUIRED(默认,加入现有事务)、REQUIRES_NEW(挂起当前,新建事务)等7种 |
| 嵌套事务 | 内层事务回滚是否影响外层? | MySQL的Savepoint机制,Spring的NESTED传播级别 |
| 多数据源事务 | 一个方法操作两个不同数据库 | 需使用分布式事务(如Seata、2PC、TCC) |
| 只读事务 | 标记事务只读,数据库可以优化 | @Transactional(readOnly = true),适用于查询 |
| 超时回滚 | 事务执行超过设定时间自动回滚 | @Transactional(timeout = 30),防止长事务锁表 |
| 回滚规则 | 默认只有RuntimeException回滚,检查异常不回滚 | 可通过rollbackFor指定:@Transactional(rollbackFor = Exception.class) |
4. 事务失效的常见场景(高频面试题)
这是实际开发中非常容易踩坑的地方,即使写了 @Transactional 也不一定生效:
| 失效场景 | 原因 | 解决方案 |
|---|---|---|
| 方法内部调用 | 同一个类内A方法(无事务)调用B方法(有事务),事务不生效 | 通过代理对象调用(自己注入自己,或AopContext.currentProxy()) |
| 异常被catch吞掉 | 方法内try-catch了异常,没有往外抛 | 要么手动回滚:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(),要么抛出异常 |
| 抛出检查异常(非RuntimeException) | 默认只对RuntimeException回滚 | 配置rollbackFor = Exception.class |
| 事务方法不是public | Spring动态代理要求方法为public | 改为public |
| 数据库引擎不支持事务 | MySQL的MyISAM引擎不支持事务 | 使用InnoDB引擎 |
| 事务传播行为配置错误 | 比如配置为Propagation.NOT_SUPPORTED |
检查传播级别 |
| Spring未启用事务管理 | 缺少@EnableTransactionManagement或事务管理器Bean |
检查配置(Spring Boot自动配置通常OK) |
最经典的失效代码:
java
@Service
public class OrderService {
public void createOrder() {
// 内部调用,事务失效!!!
this.updateStock();
}
@Transactional
public void updateStock() {
// 这个事务不会生效
}
}
5. 事务实现的原理是什么
事务的实现原理可以分为数据库层面 和框架层面两个维度。
5.1 数据库层面(以MySQL InnoDB为例)
- 原子性 :通过 Undo Log 实现。修改数据前,先将旧值写入Undo Log,回滚时用旧值覆盖。
- 持久性 :通过 Redo Log 实现。事务提交时,先写Redo Log(顺序写,性能高),再异步刷盘。即使数据库崩溃,重启后根据Redo Log恢复已提交的事务。
- 隔离性 :通过 锁 + MVCC(多版本并发控制) 实现。每行数据有多个版本(通过隐藏列DB_TRX_ID、DB_ROLL_PTR等),读操作读快照,写操作加锁。
- 一致性:是原子性、隔离性、持久性共同保证的结果。
5.2 Spring框架层面(声明式事务原理)
核心:AOP(动态代理)
- Spring 启动时,扫描所有带有
@Transactional的Bean。 - 为这些Bean创建代理对象(JDK动态代理或CGLIB)。
- 代理对象在执行目标方法前后,插入事务控制逻辑:
java
// 伪代码示意
public Object invoke(MethodInvocation invocation) {
if (方法没有@Transactional) {
return invocation.proceed();
}
// 1. 开启事务
TransactionStatus status = transactionManager.getTransaction();
try {
// 2. 执行目标方法(业务代码)
Object result = invocation.proceed();
// 3. 提交事务
transactionManager.commit(status);
return result;
} catch (Exception e) {
// 4. 满足回滚条件则回滚
transactionManager.rollback(status);
throw e;
}
}
关键组件:
- TransactionManager:事务管理器(如DataSourceTransactionManager)
- TransactionDefinition:事务属性(隔离级别、传播行为等)
- TransactionStatus:当前事务状态
总结
| 问题 | 核心答案 |
|---|---|
| 事务是什么 | 一组要么全成功要么全失败的操作,拥有ACID特性 |
| 如何使用 | SQL用BEGIN/COMMIT,Java用@Transactional |
| 特殊场景 | 传播行为、嵌套事务、多数据源、只读事务、超时等 |
| 失效原因 | 内部调用、异常被吞、非public、引擎不支持等 |
| 实现原理 | 数据库用Undo/Redo Log+MVCC;Spring用AOP动态代理 |