事务与 ACID¶
核心问题:事务的四大特性是什么?InnoDB 是如何保证每一个特性的?
它解决了什么问题?¶
没有事务,银行转账"扣款成功、入账失败"的情况就无法避免。事务保证了一组操作要么全部成功,要么全部回滚,是数据一致性的基石。
生活类比:转账必须"扣款"和"入账"同时成功或同时失败,不能只扣款不入账。这就是事务的原子性。
ACID 四大特性¶
| 特性 | 含义 | InnoDB 实现方式 | 为什么这样实现 |
|---|---|---|---|
| 原子性 Atomicity | 事务要么全成功,要么全回滚 | undo log 回滚 | undo log 记录了每个操作的逆操作,回滚时按逆序执行 |
| 一致性 Consistency | 事务前后数据满足约束 | 由其他三个特性共同保证 | 一致性是目标,原子性/隔离性/持久性是手段 |
| 隔离性 Isolation | 并发事务互不干扰 | MVCC + 锁 | MVCC 解决读写冲突,锁解决写写冲突 |
| 持久性 Durability | 提交后数据永久保存 | redo log 持久化 | redo log 先于数据页写入磁盘(WAL 机制),崩溃后可重放 |
undo log 与 redo log¶
flowchart LR
subgraph 写操作流程
T["事务开始"] --> UL["写 undo log\n记录逆操作\n用于回滚"]
UL --> RL["写 redo log\n记录变更\n用于崩溃恢复"]
RL --> BP["修改 Buffer Pool\n内存中的数据页"]
BP --> CM["事务提交\nredo log 刷盘"]
end
subgraph 崩溃恢复
CR["数据库重启"] --> RR["重放 redo log\n恢复已提交事务"]
RR --> UR["回滚 undo log\n撤销未提交事务"]
end
| 日志类型 | 作用 | 保证的特性 |
|---|---|---|
| undo log | 记录操作的逆操作,支持回滚 | 原子性 |
| redo log | 记录数据页的物理变更,支持崩溃恢复 | 持久性 |
WAL(Write-Ahead Logging)机制:先写日志,再写数据页。redo log 是顺序写(速度快),数据页是随机写(速度慢)。先写 redo log 保证了即使数据页还没落盘,崩溃后也能通过重放 redo log 恢复数据。
事务的基本使用¶
// Spring 声明式事务(推荐)
@Transactional(rollbackFor = Exception.class)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
accountMapper.deduct(fromId, amount); // 扣款
accountMapper.add(toId, amount); // 入账
// 任何异常都会触发回滚
}
// 编程式事务(需要精细控制时使用)
transactionTemplate.execute(status -> {
try {
accountMapper.deduct(fromId, amount);
accountMapper.add(toId, amount);
return null;
} catch (Exception e) {
status.setRollbackOnly(); // 标记回滚
throw e;
}
});
工作中的坑¶
坑1:事务中大批量操作导致锁等待超时¶
// ❌ 在一个事务中处理大批量数据,长时间持有行锁
@Transactional
public void batchUpdate(List<Long> ids) {
for (Long id : ids) { // ids 可能有几万条
userMapper.updateStatus(id, 1); // 每行都加行锁,持有时间极长
}
}
// ✅ 分批处理,减少锁持有时间
public void batchUpdate(List<Long> ids) {
Lists.partition(ids, 500).forEach(batch -> {
transactionTemplate.execute(status -> {
batch.forEach(id -> userMapper.updateStatus(id, 1));
return null;
});
});
}
坑2:@Transactional 不生效¶
// ❌ 同类内部调用,绕过了 Spring AOP 代理,事务不生效
@Service
public class OrderService {
public void createOrder() {
this.saveOrder(); // this 调用,不走代理
}
@Transactional
public void saveOrder() { ... }
}
// ✅ 注入自身代理,或将方法拆到另一个 Bean
面试高频问题¶
Q:ACID 四大特性分别是什么?InnoDB 如何实现?
- 原子性:undo log 支持回滚
- 一致性:由其他三个特性共同保证
- 隔离性:MVCC + 锁
- 持久性:redo log + WAL 机制
Q:undo log 和 redo log 的区别?
undo log 记录操作的逆操作,用于事务回滚,保证原子性;redo log 记录数据页的物理变更,用于崩溃恢复,保证持久性。两者配合实现了 ACID 中的 A 和 D。