数据库事务

1、什么是事务

事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。

简单理解,事务就是对数据库一连串的操作,更新、保存和删除,对数据产生变化的操作。

 

2、为什么需要事务

事务的作用是保证数据准确性和一致性的一种机制。假如没有事务,那我们提交数据不会保存,有异常的时候数据不会回滚。

比如说往银行账户存钱,余额没有添加,去ATM取钱,ATM出故障没吐钱,余额变少了。

 

3、怎么做

在JAVA开发中通常借助Spring管理事务,大致的原理就是通过AOP在方法前后添加开始事务,提交或回滚的操作。

Spring事务管理分为声明式和编程式两种。

(1)声明式一般是写在配置文件的,它的好处是配置简便,模糊匹配对应的服务方法。

也可以通过注解的方式来开始事务,不过它也是属于声明式。

(2) 编程式

看这个名字大致可以猜出是在我们业务嵌入事务管理代码,好处是事务控制粒度更加细化,缺点是对代码有侵入性。

 

使用@Transactional来声明事务

当@Transactional作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该注解来覆盖类级别的定义。

@Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

建议只在实现类或实现类的方法上使用@Transactional,而不要在接口上使用,这是因为如果使用JDK代理机制(基于接口的代理)是没问题;而使用使用CGLIB代理(继承)机制时就会遇到问题,因为其使用基于类的代理而不是接口,这是因为接口上的@Transactional注解是“不能继承的”;

@Transactional(rollbackFor = Exception.class)
@Service
public class UserServiceImpl implements UserService

 

Spring管理事务的7个传播行为

事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。比如说我们一次表单提交涉及到多个Service,

如用户信息、业务信息等等数据保存,它们本身都有自己的独立事务。

propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,默认的选择。

propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。假设有个业务方法service1,它调用了service2,

如果不希望service1的异常会导致service2回滚,就可以在service2中选择使用新建事务。

propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。

propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。

propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。

propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作

 

 

4、事务基本概念

事务的4个特性

(1)原子性(Atomicity)

事务包含的操作要不全部完成,要不都不完成。例如去银行取钱,先扣除账户余额,再添加账单流水,

这两步操作是要一起完成或者都不完成。

(2)一致性(Consistency)

一个事务执行之前和执行之后都必须处于一致性状态。如A、B账户总额有500元,不管A和B之间如何转账,

转几次账,最后两者加起来的账户总额还是500元。

(3)隔离性(Isolation)

事务之间相互不受影响,如一个事务操作A账户,另一个事务操作B账户,A账户的操作不会影响到B账户。当然也有两个事务

同时操作A账户,不过就涉及到并发问题,要对账户加锁或者做状态校验,原理是一样的,这里不加详述。

(4)持久性(Atomicity)

事务被提交后,对数据的操作改变就是永久性了,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

 

事务隔离级别

(1)脏读

脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。

比如A给B转账100元,A这个转账还没完成,B就看到自己的账户多了100元。

(2)不可重复读

同一个事务里,对同一条数据的两次查询返回了不同的结果,读到别的事务更新的数据。

A查询自己的账户余额,第一次查询是100元,第二次查询是200元。

(3) 幻影读

同一个事务里,对同一条数据的两次查询返回了不同的结果,读到别的事务添加的数据。

A查询自己名下的账户有多少个,第一次查询是1个,第二次查询是2个。

 

数据库提供的四种隔离级别:

① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。

② Repeatable read (可重复读):可避免脏读、不可重复读的发生。

③ Read committed (读已提交):可避免脏读的发生。

④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。

 

乐观锁和悲观锁

乐观锁
总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,

但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS(compare and swap)操作实现。

缺点:高并发情况下数据频繁变化时,那更新总不成功,造成资源浪费。

悲观锁
总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以要对数据加锁。

数据库的select for update,在程序代码用关键字Synchronized或显式锁Lock等等。

缺点:性能有所下降,程序复杂度较高,有死锁的风险。

 

5、分布式事务

分布式系统中涉及RPC,数据库也不一样,如何保证事务?

分布式下的数据一致性又分为强一致性和最终一致性,强一致性要求数据每次访问的时候都是最新准确的。最终一致性保证数据最终的结果是正确的

1、XA Transactions

XA是一个分布式事务协议,大致分为事务管理器和本地资源管理器,也可以称为两阶段提交事务管理器作为全局的调度者,负责各个本地资源的提交和回滚,本地资源管理器由数据库实现

(1)本地资源管理器预提交操作,并反映是否可以提交

(2)各个本地资源管理器提交

 

2、消息事务+最终一致性

消息事务就是基于消息中间件一种TCC模型,要求事务管理的业务方法提供确认(Confirm)和回滚(Cancel)两种机制,下游系统通过订阅消息来执行本地业务

(1)A系统向消息中间件发送一条预备消息
(2)消息中间件保存预备消息并返回成功
(3)A执行本地事务
(4)A发送提交消息给消息中间件
(5)若系统A在处理任务A时失败,那么就会向消息中间件发送Rollback请求
(6)消息中间件收到回滚请求后,直接将该消息丢弃不投递给系统B

 

3、分布式事务框架Seata

这是阿里开源的一款分布式事务管理框架,主要由三个重要组件组成:
Transaction Coordinator(TC):管理全局的分支事务的状态,用于全局性事务的提交和回滚。
Transaction Manager(TM):事务管理器,用于开启全局事务、提交或者回滚全局事务,是全局事务的开启者。
Resource Manager(RM):资源管理器,用于分支事务上的资源管理,向TC注册分支事务,上报分支事务的状态,接受TC的命令来提交或者回滚分支事务。

使用起来也非常简单,具体可参照相关文档。

你可能感兴趣的:(数据库)