本地事务
1.事务的基本性质
数据库事务的几个特征:原子性(atomicty)、一致性(consistency)、隔离性(isolation)、持久性(durability),简称 ACID。
- 原子性(atomicty):针对数据库的一系列操作不可拆分,要么同时成功,要么同时失败。
- 一致性(consistency):数据在事务处理的前后,业务整体一致。
- 隔离性(isolation):对数据的各种操作事务必须是彼此隔离的,事务是独立的,不会影响其他事务。
- 持久性(durability):事物完成后,对数据库的修改被永久保存。
2.并发下事务会产生的问题
脏读
所谓脏读,就是指事务A读到了事务B还没有提交的数据,具体操作如下:
- 卡内余额为1000
- A开启事务
- 切换B开启事务,取走100元,还未提交事务
- A查询余额,由于B未提交事务,当前余额还是1000元
不可重复读
所谓不可重复读,就是指在一个事务里面读取了两次某个数据,读出来的数据不一致,具体操作如下:
- A开启事务,查询余额为1000元,未提交事务
- B开启事务,取走100元,提交事务
- A再查询余额为900元,导致同一事务内两次查询结果不一致
幻读
所谓幻读,就是指在一个事务里面的操作中发现了未被操作的数据,具体操作如下:
- A开启事务,查询id>3的记录,为匹配到数据
- B开启事务,插入一条ID=4的记录
- A事务因为可重复读,再次查询还是没有ID>3的记录。(幻读)
- A事务再次插入ID=4的记录失败。
3.事务的隔离级别
DEFAULT
默认隔离级别,不同数据库的隔离级别不一样,如果Spring配置事务为DEFAULT,那么将采用底层数据库的默认事务隔离级别。
READ_UNCOMMITTED(读未提交)
该隔离级别的事务会读到其他事务未提交的数据,该机制无法解决脏读、不可重复读、幻读的问题。
READ_COMMITTED(读已提交)
该隔离级别的事务能读到其他事务已提交的数据,能防止脏读,但无法结果不可重复读和幻读问题。Oracle和SQLServer采用该默认隔离级别。
REPEATEABLE_READ(可重复读)
该隔离级别的事务,在同一事务内查询结果无论何时都是一样的,但是会产生幻读现象。
在MySQL的InnoDB引擎可以通过 next-key locks 机制来避免幻读。
SERLALIZABLE(串行化)
串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了
隔离级别 | 是否会脏读 | 是否会不可重复读 | 是否会幻读 | 加锁读 |
---|---|---|---|---|
READ_UNCOMMITTED | 是 | 是 | 是 | 否 |
READ_COMMITTED | 否 | 是 | 是 | 否 |
REPEATEABLE_READ | 否 | 否 | 是 | 否 |
SERLALIZABLE | 否 | 否 | 否 | 是 |
4.事务传播机制
- PROPAGATION_REQUIRED
- 如果当前没有事务,则创建一个新事务,如果存在事务,则加入该事务
- PROPAGATION_SUPPORTS:
- 如果当前有事务,加入该事务,如果没有事务,就以非事务执行。
- PROPAGATION_MANDATORY:
- 如果当前有事物,加入该事务,没有事务,跑出异常。
- PROPAGATION_REQUIRES_NEW:
- 无论当前是否有事务,都创建新事务。
- PROPAGATION_NOT_SUPPORTS:
- 以非事务操作执行,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER:
- 以非事务方式执行,如果当前存在事务,则跑出异常。
- PROPAGATION_NESTED:
- 嵌套在事务内还行,如果当前没有事务,则与PROPAGATION_REQUIRED机制相同。
传播行为示例
- 示例一:
@Transactional(timeout = 30)
public void a(){
this.b();//B事务传播机制为 REQUIRED 沿用 a方法事务
this.c();//C事务传播机制为 REQUIRES_NEW 创建新事务
int i = 10/0; // a b 方法事务回滚 c事务不回滚
}
@Transactional(propagation= Propagation.REQUIRED)
public void b(){
}
@Transactional(propagation= Propagation.REQUIRES_NEW)
public void c(){
}
- 示例二:
@Transactional(timeout = 30)//方法a事务设置超时时间为30秒
public void a(){
this.b();
this.c();
int i = 10/0;
}
@Transactional(propagation= Propagation.REQUIRED,timeout = 2)//方法B设置超时时间为2秒
public void b(){
try{
//因为B的传播行为为REQUIRED,会继承事务a的所有设置
Thread.sleep(7000); //休眠7秒
}catch(Exception e){
}
}
@Transactional(propagation= Propagation.REQUIRES_NEW,timeout = 5)
public void c(){
try{
//因为B的传播行为为REQUIRES_NEW,会开启新的事务配置,会导致C事务超时。
Thread.sleep(7000); //休眠7秒
}catch(Exception e){
}
}
本地事务失效问题
a()方法在同一个Spring容器管理的Bean对象内的b()、c()方法,该模式下 b()、c()方法无论做任何设置都会无效,都是和A共用一个事务,原因是因为事务是通过代理来处理的,b(),c()方法绕过了代理对象,所以不会有事务设置。
@Transactional(timeout = 30)//方法a事务设置超时时间为30秒
public void a(){
//该模式下调用b(),c()方法等同于直接调用代码块内代码
this.b();
this.c();
int i = 10/0;
}
@Transactional(propagation= Propagation.REQUIRED,timeout = 2)//方法B设置超时时间为2秒
public void b(){
try{
//因为B的传播行为为REQUIRED,会继承事务a的所有设置
Thread.sleep(7000); //休眠7秒
}catch(Exception e){
}
}
@Transactional(propagation= Propagation.REQUIRES_NEW,timeout = 5)
public void c(){
try{
//因为B的传播行为为REQUIRES_NEW,会开启新的事务配置,会导致C事务超时。
Thread.sleep(7000); //休眠7秒
}catch(Exception e){
}
}
正确解决方案:
- 引入spring-boot-starter-aop==>内部引入了aspectjweaver
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- @EnableAspectJAutoProxy:开启AspectJ动态代理功能,以后所有动态代理通过AspectJ创建(没有接口也可以创建动态代理)。
@EnableAspectJAutoProxy(exposeProxy = true)//对外暴露代理对象
- 代理对象内部调用内部方法代码修改
@Transactional(timeout = 30)//方法a事务设置超时时间为30秒
public void a(){
//该模式下调用b(),c()方法等同于直接调用代码块内代码
//this.b();
//this.c();
UserServiceImpl userService = (UserServiceImpl)AopContext.currentProxy();
userService.b();
userService.c();
int i = 10/0;
}
@Transactional(propagation= Propagation.REQUIRED,timeout = 2)//方法B设置超时时间为2秒
public void b(){
try{
//因为B的传播行为为REQUIRED,会继承事务a的所有设置
Thread.sleep(7000); //休眠7秒
}catch(Exception e){
}
}
@Transactional(propagation= Propagation.REQUIRES_NEW,timeout = 5)
public void c(){
try{
//因为B的传播行为为REQUIRES_NEW,会开启新的事务配置,会导致C事务超时。
Thread.sleep(7000); //休眠7秒
}catch(Exception e){
}
}
分布式事务
1.为什么会有分布式事务
分布式系统经常出现的异常有
- 机器宕机
- 网络异常
- 消息丢失
- 消息乱序
- 数据错误
- 不可靠的TCP
- 存储数据就是
- …等问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vhx5UAKN-1622015641056)(http://106.75.145.234/upload/2021/05/image-b60e17a2751141e4af0ce66782855096.png)]
分布式事务出现的原因就是节点之间互相状态不能同步,互相感知不到。
2.CAP定理与BASE理论
2.1 CAP定理
CAP原则又称CAP定理,指的是在一个分布式系统中,不可能同事满足以下三点。
选项 | 具体意义 |
---|---|
一致性(Consistency) | 所有节点访问时都是同一份最新的数据副本 |
可用性(Availability) | 每次请求都能获取到非错的响应,但是不保证获取的数据为最新数据 |
分区容错性(Partition tolerance) | 分布式系统在遇到任何网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务,除非整个网络环境都发生了故障 |
- 一致性(C)
这里指强一致性,在写操作完成后,无论在哪个节点再次获取对象都返回的是刚写入的值。
- 客户端随机调用服务器G1写入数据v1,等待响应
- 此时G1数据为v1,G2数据为v0数据不一致
- 响应客户端之前G2服务器自动同步G1服务器的数据,现在G2数据也等于v1
- G2同步完数据后通知G1
- G1接收同步完成命令,响应客户端“数据写入成功”
- 客户端读取数据,随机分配到G2服务器进行读取
- 此时获取的数据为v1,为客户端刚向G1写入的数据。
- 可用性(A)
保证每个请求不管成功或者失败都有响应。 - 分区容忍性(P):系统中任意信息的丢失或失败不会影响系统的继续运作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GwJMDWIt-1622015641060)(http://106.75.145.234/upload/2021/05/image-2fe6888480734fdc91405a61307a6aea.png)]
如上图所述,分布式系统下分区容错是无法避免的,只能选择AP、或者CP。
2.2 BASE理论
上面讲到CAP不可能同时满足,由于分区容错性是必须的,为了尽可能满足CAP特性,所以出现了BASE理论。
BASE:全称Basically Available(基本可用),Soft state(软状态),Eventually
Consistent(最终一致性)三个短语的缩写,是基于CAP定理逐步演化而来。
BASE理论核心思想:无法做到强一致,每个应用结合自身业务特点,采用适当方式是系统达成最终一致。
- Basically Available (基本可用)
基本可用表示,当系统出现了不可预知的故障,但是还能用,以下为“基本可用”典型例子:
- 响应时间上的损失:正常情况下搜索引擎要0.5秒响应用户查询结果。但由于异常情况,响应时间追加到1~2秒。
- 功能上的损失:在电商平台上,正常情况用户可以顺利完成下单,但是在活动高峰的时候,为了保护系统的稳定性,部分消费者可能会引导到一个降级页面。
- Soft state(软状态)
和“软状态”相对应的是“硬状态”
“硬状态”要求多个节点的数据副本都是一致的。
“软状态”允许数据存在中间状态,并认为该状态不会影响系统的整体可用性,也就是允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。 - Eventually Consistent(最终一致性)
最终一致性强调的是系统中所有的数据副本,在经过一段时间后,最终达到一个统一状态。这个时间取决于网络延时、系统负载、数据复制方案设计等因素。