3、传统单体事务模型:ACID特性、本地事务与全局事务、在事件风暴画布中建模单体事务
在分布式事务架构演进中,单体事务模型是理解后续所有复杂模式的基石。本章将系统梳理ACID理论、本地事务与全局事务的差异,并展示如何在事件风暴画布中为单体应用建立事务边界。
3.1 ACID特性:事务的四大黄金法则
ACID是数据库事务正确执行的四个基本特性,由Haerder与Reuter在1983年正式提出。任何事务处理系统都必须满足这四项约束,才能保证数据的一致性与可靠性。
| 特性 | 全称 | 核心含义 | 典型实现机制 |
|---|---|---|---|
| A | Atomicity(原子性) | 事务中的所有操作要么全部成功提交,要么全部失败回滚,不存在中间状态 | Undo日志、Redo日志、事务日志 |
| C | Consistency(一致性) | 事务执行前后,数据库必须满足所有预定义的完整性约束(主键、外键、唯一约束、业务规则) | 约束检查、触发器、应用层校验 |
| I | Isolation(隔离性) | 并发执行的事务之间互不干扰,每个事务都感觉自己是唯一运行的 | 锁机制、MVCC(多版本并发控制) |
| D | Durability(持久性) | 一旦事务提交成功,其对数据库的修改就是永久性的,即使系统崩溃也不会丢失 | WAL(预写式日志)、非易失性存储 |
实践提示:在单体应用中,ACID由数据库引擎直接保障。开发者只需声明事务边界(BEGIN/COMMIT/ROLLBACK),无需关心底层实现。这是单体架构在数据一致性方面的最大红利。
3.2 本地事务与全局事务
根据事务涉及的数据源范围,传统事务模型分为本地事务和全局事务两类。理解两者的区别,是后续向分布式事务演进的前提。
3.2.1 本地事务(Local Transaction)
本地事务是指仅操作单一数据库资源的事务。它由数据库自身的事务管理器(如MySQL的InnoDB引擎、Oracle的Undo表空间)直接管理,不涉及外部协调者。
- 典型场景:单体应用连接单个MySQL实例,执行订单插入+库存扣减+账户扣款(三张表在同一数据库)。
- 实现方式:通过JDBC的
Connection.setAutoCommit(false)+commit()/rollback()。 - 优势:性能高(无网络开销)、实现简单、ACID完全由数据库保证。
- 局限:无法跨数据库、跨消息队列、跨HTTP服务。
// 本地事务示例(Java JDBC)
Connection conn = dataSource.getConnection();
try {
conn.setAutoCommit(false);
// 操作1:插入订单
orderDao.insert(order);
// 操作2:扣减库存
inventoryDao.decrease(productId, quantity);
// 操作3:扣减账户余额
accountDao.deduct(userId, amount);
conn.commit(); // 全部成功,提交
} catch (Exception e) {
conn.rollback(); // 任一失败,全部回滚
} finally {
conn.close();
}
3.2.2 全局事务(Global Transaction / XA Transaction)
全局事务是指跨多个资源管理器(如多个数据库、消息队列、文件系统)的事务。它需要引入一个外部协调者——事务管理器(Transaction Manager),通过两阶段提交(2PC)协议保证所有参与者的原子性。
- 典型场景:单体应用同时操作MySQL数据库和RabbitMQ消息队列,要求两者保持最终一致。
- 实现方式:JTA(Java Transaction API)+ XA协议,或.NET的System.Transactions。
- 优势:能够跨异构资源保证ACID。
- 局限:性能差(同步阻塞、锁持有时间长)、实现复杂、对资源管理器有XA兼容性要求、存在协调者单点故障风险。
架构避坑:全局事务(XA/2PC)在单体架构中已显笨重,在微服务架构中更是灾难。后续章节将看到,分布式系统通常放弃全局事务,转而采用最终一致性+补偿机制。
3.3 在事件风暴画布中建模单体事务
事件风暴(Event Storming)是一种协作建模技术,通过“事件-命令-聚合-策略”等元素可视化业务流。在单体事务场景中,我们关注的是:如何在一个事务边界内,将多个领域事件和命令映射到同一个数据库事务中。
3.3.1 建模步骤
- 识别领域事件(Domain Event):橙色便签,记录业务中发生的事实(如“订单已创建”“库存已扣减”“账户已扣款”)。
- 识别命令(Command):蓝色便签,触发事件的用户意图或系统动作(如“提交订单”“扣减库存”“扣款”)。
- 识别聚合(Aggregate):黄色便签,命令执行的目标实体(如“订单”“库存”“账户”)。
- 划定事务边界(Transaction Boundary):用红色虚线框将必须在一个ACID事务中完成的所有操作圈在一起。
- 标注策略(Policy):紫色便签,描述事务失败时的补偿或重试规则(单体事务中通常为“回滚并抛出异常”)。
3.3.2 单体事务建模示例
以下SVG图展示了一个典型的“下单扣库存扣款”单体事务在事件风暴画布中的建模结果。所有操作被红色虚线框包围,表示它们共享同一个数据库事务。
3.3.3 单体事务建模要点
- 事务边界必须包含所有写操作:任何对数据库状态的修改(INSERT/UPDATE/DELETE)都必须落在边界内。
- 读操作可以游离在边界外:查询类命令不需要事务保护,除非要求“可重复读”隔离级别。
- 避免跨边界调用:如果某个命令需要调用外部HTTP服务或消息队列,则无法使用单体事务——这是后续分布式事务的切入点。
- 策略(Policy)通常为“回滚”:在单体事务中,失败策略几乎总是“全部回滚并抛出异常”,由上层调用方决定是否重试。
3.4 单体事务的适用边界与演进信号
单体事务并非万能。以下表格帮助判断何时应该坚持单体事务,何时应该考虑向分布式事务演进。
| 判断维度 | 适合单体事务 | 需要演进(分布式事务) |
|---|---|---|
| 数据源数量 | 单一数据库 | 多个数据库、消息队列、缓存、文件系统 |
| 服务架构 | 单体应用(Monolith) | 微服务、服务网格、无服务器架构 |
| 一致性要求 | 强一致性(ACID) | 最终一致性(BASE) |
| 事务持续时间 | 毫秒级(短事务) | 秒级或更长(长事务/柔性事务) |
| 性能敏感度 | 高(本地事务开销极低) | 可接受一定延迟(2PC、Saga、TCC) |
| 团队规模 | 小团队(< 10人) | 多团队协作(> 10人) |
关键认知:在事件风暴画布中,如果某个事务边界内出现了“发送邮件”“调用第三方API”“写入消息队列”等操作,说明单体事务已经无法满足需求。此时,要么将边界缩小(只保护数据库操作),要么引入分布式事务模式(如Saga、TCC、事件驱动)。这正是本课程后续章节要深入探讨的内容。