数据库事务

→ 返回 关系型数据库

事务(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 与锁(概要)

并发控制的两大支柱:

机制作用详情
MVCC快照读不加锁,通过版本链 + ReadView 判断可见性MVCC 详解
锁当前读 / 写操作互斥,行锁、间隙锁、Next-Key Lock锁机制详解

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

相关