数据库事务
事务(Transaction)是一组数据库操作的逻辑单元,要么全部成功,要么全部回滚,保证数据的一致性与完整性。
ACID 四大特性
| 特性 | 含义 | 实现方式(InnoDB) |
|---|---|---|
| 原子性(Atomicity) | 事务内所有操作要么全成功,要么全回滚 | undo log |
| 一致性(Consistency) | 事务执行前后数据满足所有约束(业务规则) | 其余三者共同保证 |
| 隔离性(Isolation) | 并发事务互不干扰,中间状态对外不可见 | MVCC + 锁 |
| 持久性(Durability) | 事务提交后数据永久写入,宕机不丢失 | redo log(WAL) |
并发问题
| 问题 | 描述 | 示例 |
|---|---|---|
| 脏读 | 读到另一事务未提交的数据 | T2 读到 T1 修改但未提交的金额 |
| 不可重复读 | 同一事务内两次读同一行,结果不同(另一事务修改并提交) | T1 两次读余额,中间 T2 扣款提交 |
| 幻读 | 同一事务内两次查询,行数不同(另一事务插入/删除并提交) | T1 两次查用户数,中间 T2 新增一行 |
| 丢失更新 | 两个事务读取同一行后各自修改,后提交者覆盖前者 | 并发抢购库存 |
四种隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
|---|---|---|---|---|
| READ UNCOMMITTED | ❌ 可能 | ❌ 可能 | ❌ 可能 | 最低,几乎不用 |
| READ COMMITTED | ✅ 防止 | ❌ 可能 | ❌ 可能 | Oracle/SQL Server 默认 |
| REPEATABLE READ | ✅ 防止 | ✅ 防止 | ⚠️ 部分防止 | MySQL InnoDB 默认(间隙锁防幻读) |
| SERIALIZABLE | ✅ 防止 | ✅ 防止 | ✅ 防止 | 最高,串行执行,性能差 |
-- 查看当前隔离级别
SELECT @@transaction_isolation;
-- 设置当前会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;MVCC 与锁(概要)
并发控制的两大支柱:
InnoDB 默认 REPEATABLE READ:快照读靠 MVCC 避免不可重复读;当前读靠 Next-Key Lock 抑制幻读。
分布式事务
单机事务无法跨越多个数据库/服务,需要分布式事务协议。
XA(两阶段提交,2PC)
Coordinator(协调者)
│
│ Phase 1:PREPARE
├──► Participant A(prepare → vote YES/NO)
└──► Participant B(prepare → vote YES/NO)
│
│ Phase 2:COMMIT or ROLLBACK
├──► Participant A
└──► Participant B
优点:强一致性,ACID 保证
缺点:协调者单点故障,参与者长时间锁定资源,性能差;同步阻塞
-- MySQL XA 示例
XA START 'xid1';
UPDATE account SET balance = balance - 100 WHERE id = 1;
XA END 'xid1';
XA PREPARE 'xid1';
-- 所有参与者 PREPARE 成功后
XA COMMIT 'xid1';SAGA 模式
将长事务拆分为多个本地事务,每步都有对应的补偿事务:
T1 → T2 → T3 → 成功
T1 → T2 → T3 失败 → C3 → C2 → C1(补偿回滚)
- 优点:无长时锁,高可用,适合微服务
- 缺点:实现复杂,存在中间状态(最终一致)
- 实现方式:编排(Orchestration,由 Saga Coordinator 协调)或 编舞(Choreography,各服务通过事件通信)
TCC 模式(Try-Confirm-Cancel)
| 阶段 | 说明 |
|---|---|
| Try | 预留资源(冻结库存、预扣金额),不真正执行 |
| Confirm | 正式提交(扣减库存、扣款完成) |
| Cancel | 释放预留资源(解冻库存、退款) |
优点:最终一致,业务灵活
缺点:业务侵入性强,每个接口需实现三个方法;需处理幂等和空回滚
本地消息表
业务 DB 写入 → 本地消息表(同一事务)
↓
定时任务扫描消息表
↓
发送到 MQ → 消费者处理 → 更新消息状态
优点:实现简单,可靠性高
缺点:消息表是性能瓶颈,与业务 DB 强耦合
方案选型
| 方案 | 一致性 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| XA/2PC | 强 | 低 | 低 | 数据库内部,少量参与者 |
| TCC | 最终 | 高 | 高 | 核心支付,资金场景 |
| SAGA | 最终 | 高 | 中 | 长流程业务(订单、物流) |
| 本地消息表 | 最终 | 中 | 低 | 跨服务异步通知 |
| 最大努力通知 | 弱 | 高 | 低 | 通知类、对账容忍 |
Spring @Transactional 传播行为
| 传播行为 | 说明 | 典型场景 |
|---|---|---|
REQUIRED(默认) | 有事务则加入,没有则新建 | 绝大多数 Service 方法 |
REQUIRES_NEW | 总是新建独立事务,挂起外层事务 | 记录审计日志(不随业务回滚) |
NESTED | 嵌套事务,外层回滚时内层也回滚,内层回滚不影响外层 | 批处理中单条可独立回滚 |
SUPPORTS | 有事务则加入,没有则以非事务方式执行 | 只读查询 |
NOT_SUPPORTED | 始终以非事务方式运行,挂起外层事务 | 批量查询,避免长事务 |
MANDATORY | 必须在事务中执行,否则抛异常 | 内部工具方法,强制调用方开事务 |
NEVER | 不允许在事务中执行,否则抛异常 | — |
常见坑
| 问题 | 原因 | 解决 |
|---|---|---|
@Transactional 不生效 | 同类内部方法调用(绕过代理) | 注入自身 Bean 或抽到独立类 |
| 事务不回滚 | 默认只回滚 RuntimeException,checked exception 不回滚 | 添加 rollbackFor = Exception.class |
| 长事务 | Service 方法持锁时间过长,包含 HTTP 调用、MQ 发送等 | 事务内只做 DB 操作,IO 操作移到事务外 |
| 读写分离失效 | 事务中的读操作路由到主库 | 只读事务用 @Transactional(readOnly = true) |
| 幂等性缺失 | MQ 消费者、定时任务重复执行写入 | 数据库唯一约束 + INSERT IGNORE |
相关
- SQL 基础查询 — SELECT / DML / JOIN 写法
- MVCC — 多版本并发控制、ReadView、可见性
- 锁机制 — 行锁、间隙锁、死锁
- MySQL — InnoDB 事务实现详解
- PostgreSQL MVCC 与事务
- Spring Boot 事务管理 —
@Transactional详细用法 - Seata — 分布式事务框架(AT/TCC/SAGA/XA 模式)
- 数据传输 — 迁移过程中的事务一致性