基础概念
1.1.事务
事务可以看做是一次大的活动,它由不同的小活动组成,这些活动要么全部成功,要么全部失败。
1.2.本地事务
在计算机系统中,更多的是通过关系型数据库来控制事务,利用数据库本身的事务特性来实现,因此叫数据库事务,由于应用主要靠关系数据库来控制事务,而数据库通常和应用在同一个服务器,所以基于关系型数据库的事务又被称为本地事务。
数据库事务的四大特性 ACID:
-
A(Atomic)原子性
构成事务的所有操作,要么都执行完成,要么全部不执行,不可能出现部分成功部分失败的情况。
-
C(Consistency)一致性
在事务执行前后,数据库的一致性约束没有被破坏。比如:张三向李四转100元, 转账前和转账后的数据是正确状态这叫一致性,如果出现张三转出100元,李四账户没有增加100元这就出现了数据错误,就没有达到一致性。
-
I(Isolation)隔离性
数据库中的事务一般都是并发的,隔离性是指并发的两个事务的执行互不干扰,一个事务不能看到其他事务运行过程的中间状态。通过配置事务隔离级别可以避脏读、重复读等问题。
-
D(Durability)持久性
事务完成之后,该事务对数据的更改会被持久化到数据库,且不会被回滚。 数据库事务在实现时会将一次事务涉及的所有操作全部纳入到一个不可分割的执行单元,该执行单元中的所有操作要么都成功,要么都失败,只要其中任一操作执行失败,都将导致整个事务的回滚。
1.3事务的属性
1.传播行为(事务的传递):
2.隔离级别(控制并发的弊端):
3.只读(优化);
4.超时(释放资源);
5.回滚规则(指定要不要再出错后回滚事务);
1.4使用Spring注解管理传播行为
// 如果有事务,那么加入事务,没有的话新建一个(默认)
1)@Transactional(propagation=Propagation.REQUIRED)
// 容器不为这个方法开启事务(如果有事务将其挂起,执行方法)
2)@Transactional(propagation=Propagation.NOT_SUPPORTED)
// 不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
3)@Transactional(propagation=Propagation.REQUIRES_NEW)
// 必须在一个已有的事务中执行,否则抛出异常
4)@Transactional(propagation=Propagation.MANDATORY)
// 必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
5)@Transactional(propagation=Propagation.NEVER)
// 如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.
6)@Transactional(propagation=Propagation.SUPPORTS)
//内嵌到一个事务里面,当运行到内部Transaction时会停止,执行内部的Transaction 等其执行完了才执行外部的;
7)@Transactional(propagation=Propagation.NESTED)
/*
public void methodName(){
// 本类的修改方法 1
update();
// 调用其他类的修改方法
otherBean.update();
// 本类的修改方法 2
update();
}
other失败了不会影响 本类的修改提交成功
本类update的失败,other也失败
*/
1.5事务中经常出现的并发问题
脏读:一个事务读取了另一个事务操作但未提交的数据。
不可重复读:一个事务中的多个相同的查询返回了不同数据。
幻读:事务并发执行时,其中一个事务对另一个事务中操作的结果集的影响。
1.6SQL规范定义的四个事务隔离级别
以上事务中经常发生的问题,为了兼顾并发效率和异常控制,SQL规范定义了四个事务隔离级别:
-
Read uncommitted (读未提交)
如果设置了该隔离级别,则当前事务可以读取到其他事务已经修改但还没有提交的数据。这种隔离级别是最低的,会导致上面所说的脏读
-
Read committed (读已提交)
如果设置了该隔离级别,当前事务只可以读取到其他事务已经提交后的数据,这种隔离级别可以防止脏读,但是会导致不可重复读和幻读。这种隔离级别最效率较高,并且不可重复读和幻读在一般情况下是可以接受的,所以这种隔离级别最为常用。
-
Repeatable read (可重复读、默认)
如果设置了该隔离级别,可以保证当前事务中多次读取特定记录的结果相同。可以防止脏读、不可重复读,但是会导致幻读。
-
Serializable(串行化)
如果设置了该隔离级别,所有的事务会放在一个队列中执行,当前事务开启后,其他事务将不能执行,即同一个时间点只能有一个事务操作数据库对象。这种隔离级别对于保证数据完整性的能力是最高的,但因为同一时刻只允许一个事务操作数据库,所以大大降低了系统的并发能力。
按隔离级别由弱到强:
1.7查看事务隔离级别
命令行登录mysql,查看当前事务隔离级别:
select @@tx_isolation; 或者 select @@session.tx_isolation;
1.8Spring事务配置
-
只读
// readOnly=true只读,不能更新,删除
@Transactional (propagation = Propagation.REQUIRED,readOnly=true)
-
超时;释放资源
// 设置超时时间
@Transactional (propagation = Propagation.REQUIRED,timeout=30)
-
回滚规则
默认throw new RuntimeException("...");会回滚
需要捕获的throw new Exception("...");不会回滚
// 指定回滚
@Transactional(rollbackFor=Exception.class)
public void methodName() {
// 会回滚
throw new Exception("...");
// 会回滚
throw new RuntimeException("...");
}
//指定不回滚
@Transactional(noRollbackFor=Exception.class)
public ItimDaoImpl getItemDaoImpl() {
// 会回滚
throw new RuntimeException("注释");
}
//rollbackFor 默认是 RuntimeException
@Transactional(rollbackFor=class)//@Transactional()
public ItimDaoImpl getItemDaoImpl() {
// 不会回滚
throw new Exception("...");
// 会回滚
throw new RuntimeException("");
}
本地事务
Begin transaction
update A set amount = amount-100 where user_id = 1;
update B set amount = amount+100 where user_id = 1;
end transaction
commit;
使用spring一个注解搞定上述事务功能
@Transactional(rollbackFor=Exception.class)
public voud update() {
updateA();
updateB();
}
1.9分布式事务
随着互联网的快速发展,软件系统由原来的单体应用转变为分布式微服务应用。事务由于网络问题远程调用等导致数据不一致,即分布式事务问题。
2.0分布式事务产生的场景
-
跨JVM进程产生分布式事务
典型的场景就是微服务架构 微服务之间通过远程调用完成事务操作。
-
跨数据库实例产生分布式事务
单体系统访问多个数据库实例 当单体系统需要访问多个数据库(实例)时就会产生分布式事务。 比如:用户信 息和订单信息分别在两个MySQL实例存储,用户管理系统删除用户信息,需要分别删除用户信息及用户的订单信 息,由于数据分布在不同的数据实例,需要通过不同的数据库链接去操作数据,此时产生分布式事务。
-
多服务访问同一个数据库实例
跨JVM进程,两个微服务持有了不同的数据库连接进行数据库操作,此时产生分布式事务。
2.1CAP理论
CAP是 Consistency、Availability、Partition tolerance三个词语的缩写,分别表示一致性、可用性、分区容错性。
-
C - Consistency一致性
一致性是指写操作后的读操作可以读取到最新的数据状态,当数据分布在多个节点上,从任意结点读取到的数据都是最新的状态。
-
A - Availability可用性
可用性是指任何事务操作都可以得到响应结果,且不会出现响应超时或响应错误。
-
P - Partition tolerance分区容错性
分布式系统的各结点部署在不同的子网,不可避免的会出现由于网络问题而导致结点之间通信失败,此时仍可对外提供服务。
CAP 不可能都取,只能取其中2个的原因如下:
- 如果C是第一需求的话,那么会影响A的性能,因为要数据同步,不然请求结果会有差异,但是数据同步会消耗时间,期间可用性就会降低。
- 如果A是第一需求,那么只要有一个服务在,就能正常接受请求,但是对于返回结果变不能保证,原因是,在分布式部署的时候,数据一致的过程不可能想切线路那么快。
- 再如果,同时满足一致性和可用性,那么分区容错就很难保证了,也就是单点,也是分布式的基本核心。
2.2Base理论
BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent (最终一致性)三个短语的缩 写。BASE理论是对CAP中AP的一个扩展,通过牺牲强一致性来获得可用性,当出现故障允许部分不可用但要保证 核心功能可用,允许数据在一段时间内是不一致的,但最终达到一致状态。满足BASE理论的事务,称之为“柔 性事务”。
-
基本可用Basically Available
分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。如,电商网站交易付款出 现问题了,商品依然可以正常浏览。
-
软状态Soft state
由于不要求强一致性,所以BASE允许系统中存在中间状态(也叫软状态),这个状态不影响系统可用 性,如订单的"支付中"、“数据同步中”等状态,待数据最终一致后状态改为“成功”状态。
-
最终一致Eventually consistent
最终一致是指经过一段时间后,所有节点数据都将会达到一致。如订单的"支付中"状态,最终会变 为“支付成功”或者"支付失败",使订单状态与实际交易结果达成一致,但需要一定时间的延迟、等待。
2.3分布式事务解决方案
2PC(两阶段提交)
2PC即两阶段提交协议,是将整个事务流程分为两个阶段,准备阶段(Prepare phase)、提交阶段(commit phase),2是指两个阶段,P是指准备阶段,C是指提交阶段。
- 准备阶段(Prepare phase):事务管理器给每个参与者发送Prepare消息,每个数据库参与者在本地执行事 务,并写本地的Undo/Redo日志,此时事务没有提交。(Undo日志是记录修改前的数据,用于数据库回滚,Redo日志是记录修改后的数据,用于提交事务后写入数据文件)
- 提交阶段(commit phase):如果事务管理器收到了参与者的执行失败或者超时消息时,直接给每个参与者 发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据事务管理器的指令执行提交或者回滚操作,并释放事务处理过程中使用的锁资源。注意:必须在最后阶段释放锁资源。
2PC两段提交的缺点
二阶段提交看似能够提供原子性的操作,但它存在着的缺陷
-
网络抖动导致的数据不一致: 第二阶段中
协调者
向参与者
发送commit
命令之后,一旦此时发生网络抖动,导致一部分参与者
接收到了commit
请求并执行,可其他未接到commit
请求的参与者
无法执行事务提交。进而导致整个分布式系统出现了数据不一致。 -
超时导致的同步阻塞问题:
2PC
中的所有的参与者节点都为事务阻塞型
,当某一个参与者
节点出现通信超时,其余参与者
都会被动阻塞占用资源不能释放。 -
单点故障的风险: 由于严重的依赖
协调者
,一旦协调者
发生故障,而此时参与者
还都处于锁定资源的状态,无法完成事务commit
操作。虽然协调者出现故障后,会重新选举一个协调者,可无法解决因前一个协调者
宕机导致的参与者
处于阻塞状态的问题。
3PC(三阶段提交)
三段提交(3PC)是对两段提交(2PC)的一种升级优化,3PC
在2PC
的第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前,各参与者节点的状态都一致。同时在协调者和参与者中都引入超时机制,当参与者
各种原因未收到协调者
的commit请求后,会对本地事务进行commit,不会一直阻塞等待,解决了2PC
的单点故障问题,但3PC
还是没能从根本上解决数据一致性的问题。
3PC 的三个阶段分别是CanCommit
、PreCommit
、DoCommit
- CanCommit:协调者向所有参与者发送CanCommit命令,询问是否可以执行事务提交操作。如果全部响应YES则进入下一个阶段。
- PreCommit:
协调者
向所有参与者
发送PreCommit
命令,询问是否可以进行事务的预提交操作,参与者接收到PreCommit请求后,如参与者成功的执行了事务操作,则返回Yes
响应,进入最终commit阶段。一旦参与者有向协调者发送了No
响应,或因网络造成超时,协调者没有接到参与者的响应,协调者向所有参与者发送abort
请求,参与者接受abort命令执行事务的中断。 - DoCommit:在前两个阶段中所有参与者的响应反馈均是
YES
后,协调者向参与者发送DoCommit
命令正式提交事务,如协调者没有接收到参与者发送的ACK响应,会向所有参与者发送abort
请求命令,执行事务的中断。
XA方案
基于2PC实现的规范,是强一致性分布式事务。由数据实现的2PC规范,关系型数据库有MySQl、Oracle等。
XA 规范:
xa_start: 负责开启或者恢复一个事务分支,并且管理 XID 到调用线程。
xa_end: 负责取消当前线程与事务分支的关联。
xa_prepare: 询问 RM 是否准备好提交事务分支。
—————— 第一阶段提交 —————————
如果是单机,可以直接跳过 prepare 和第二阶段,输入 one phase commit 事务id 直接进行提交即可。
xa_commit: 通知 RM 提交事务分支。
xa_rollback: 通知 RM 回滚事务分支。
xa_recover: 需要恢复的 XA 事务。
—————— 第二阶段提交 —————————
XA 二阶段提交:
一阶段:执行 XA PREPARE 语句。
二阶段:执行 XA COMMIT/ROLLBACK 语句。
XA
协议存在的问题
-
同步阻塞问题:一般情况下,不需要调高隔离级别,
XA
默认不会改变隔离级别。 -
单点故障:成熟的
XA
框架需要考虑TM
的高可用性 -
数据不一致:极端情况下,一定有事务失败问题,需要监控和人工处理
解决 XA
数据不一致方案:
- 日志存储:记录
XA
事务在每个流程中的执行状态。 - 自定义事务恢复:通过
XA recovery
命令从资源管理器中获取需要被恢复的事务记录,然后根据XID
匹配应用程序中存储的日志,根据事务状态进行提交或回滚。
本地消息表
将分布式事务拆分成本地事务进行处理,保证最终一致性。
处理流程:
- 事务发起方把要处理的业务事务和写消息表这两个操作放在同一个本地事务里。
- 事务发起方有一个定时任务轮询消息表,把没有处理的消息发送到消息中间件。
- 事务被动方从消息中间件获取到消息后,返回成功。
- 事务发起方更新消息状态为已成功。
注意:消息的接收方即事务被动方需要做幂等。
create table tx_message
(
id bigint auto_increment primary key,
orderly tinyint not null comment '是否为顺序消息',
topic varchar(64) not null comment 'MQ topic',
sharding_key varchar(128) not null comment 'ShardingKey,用于选择不同的 partition',
tag varchar(128) not null comment 'Message Tag 信息',
msg_id varchar(64) not null comment 'Msg ID 只有发送成功后才有数据',
msg_key varchar(64) not null comment 'MSG Key,用于查询数据',
msg longtext not null comment '要发送的消息',
retry_time tinyint not null comment '重试次数',
status tinyint not null comment '发送状态:0-初始化,1-发送成功,2-发送失败',
create_time datetime not null,
update_time datetime not null,
index idx_update_time_status(update_time, status)
);
事务消息
将本地消息表存储和扫描事务消息的事,由消息中间件RocketMQ完成。支持事务消息的消息中间件只有RocketMQ。
实现流程:
- 发起方发送半事务消息给RocketMQ,此时消息的状态为prepare,接收方还不能拉渠道此消息。
- 发起方进行本地事务操作。
- 发起方给RocketMQ确认提交消息,接受消费此消息。
TCC补偿事务
TCC
(Try-Confirm-Cancel)又被称补偿事务,是
应用层面的2PC
,需要编写业务逻辑来实现。
Try阶段:
业务操作时通过Try操作去操作数据库预留资源。
Confirm阶段:
确认执行业务操作,在只预留的资源基础上,发起操作请求。
Cancel阶段:
只要涉及到的相关业务中,有一个业务方预留资源未成功,则取消所有业务资源的预留请求。
TCC的缺点:
-
应用侵入性强:TCC由于基于在业务层面,致使每个操作都需要有
try
、confirm
、cancel
三个接口。 -
开发难度大:代码开发量很大,要保证数据一致性
confirm
和cancel
接口还必须实现幂等性。
Saga
Saga是一个长活事务可被分解成可以交错运行的子事务集合。其中每个子事务都是一个保持数据库一致性的真实事务。
-
每个Saga由一系列sub-transaction Ti 组成
-
每个Ti 都有对应的补偿动作Ci,补偿动作用于撤销Ti造成的结果
Saga没有Try,直接commit。Saga不提供ACID保证,因为原子性和隔离性不能得到满足。通过saga log,saga可以保证一致性和持久性。Saga保证所有的子事务都得以完成或补偿,但Saga系统本身也可能会崩溃。
Saga的实现:
- Saga状态机实现。
- Saga AOP Proxy实现。
Saga模式适用于业务流程长、业务流程多的业务;在银行金融机构使用广泛。
seata
Seata 是一款阿里开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 提供了 AT、TCC、SAGA 和 XA 事务模式。