数据库事务
一.数据库事务的简介
1.什么是事务?
数据库事务就是在进行数据库操作的时候,单条语句是原子操作的,但是多条语句不是原子操作,可能会导致一部分语句执行成功,一部分语句执行失败,但是某些业务场景下我们需要保证多条语句同时执行成功或者执行失败.
2.事务的特性
- 原子性(Atomicity):事务中的全部操作在数据库中是不可分割的,要么全部完成,要么全部不执行。
- 一致性(Consistency):几个并行执行的事务,其执行结果必须与按某一顺序 串行执行的结果相一致。
- 隔离性(Isolation):事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须是透明的。
- 持久性(Durability):对于任意已提交事务,系统必须保证该事务对数据库的改变不被丢失,即使数据库出现故障。
3.事务的操作
(1) 开启事务
开启事务是事务开始的操作,接下来的数据库操作将是原子操作,要么成功要么失败.
(2) 提交事务
提交事务:将当前事务提交到数据库,真正的实现数据存储在数据库中.
(3) 事务回滚
如果在事务中,某一条或者几条数据库操作语句执行失败,则统一的回滚所有的sql语句到执行事务之前的状态.
4.事务的分类
如果一个事务中,执行的sql数据库操作在同一个数据库中,则我们称之为单库,数据库事务;如果一个事务中的sql数据库操作在多个数据库中,则我们称为分布式事务操作.
(1) 单库事务
1) 解释
事务中所有的sql语句是操作的同一个数据库.且市场上很多数据库本身就支持单库的事务.
2) 常用支持事务的数据库
- mysql(innoDB引擎)
- oracle
- redis(NOsql数据库)
(2) 多库事务(分布式事务)
1) 解释
事务中的所有sql是操作的不同的数据库,即数据库事务并不是由单一的某一个库能控制提交或者回滚的.
2) 多库操作,事务解决方案
在接下来的文章中详细解释.
二.单库事务的方案
单库事务的编写方式:
1.数据库直接操作
略
2.java代码硬编码
略
3.@Transaction注释事务
@Transactional注解是spring利用AOP的方式实现事务控制的注解.
(1) 事务编写
- 在类上标注注解@Transactional
表示本类的所有方法都开启事务操作.
@Service
@Transactional(readOnly = false, //是否是查询只读
isolation = Isolation.READ_COMMITTED, //隔离级别
rollbackFor =Throwable.class, //回滚异常类型
timeout = 30, //超时时间
propagation = Propagation.REQUIRED) //事务的传播特性,意思就是事务中调用其他事务方法,则其他事物方法是否归属在现有这个事务中
public class SysUserServiceImpl implements SysUserService {
- 在方法上表示注解@Transactional
表示本方法,开启事务
@Transactional(readOnly = true)
@Override
public Map<String,Object> findObjectById(Integer id) {
System.out.println("==findObjectById==");
SysUserDeptVo user = sysUserDao.findObjectById(id);
List<Integer> roleIds = sysUserRoleDao.findRoleIdsByUserId(id);
....
return map;
}
(2) @Transactional事务的属性
@Service
@Transactional(readOnly = false, //是否是查询只读
isolation = Isolation.READ_COMMITTED, //隔离级别
rollbackFor =Throwable.class, //回滚异常类型
timeout = 30, //超时时间
propagation = Propagation.REQUIRED) //事务的传播特性,意思就是事务中调用其他事务方法,则其他事物方法是否归属在现有这个事务中
public class SysUserServiceImpl implements SysUserService {
-
readOnly
是否是查询,只读 -
isolation
事务的隔离级别 -
rollbackFor
事务回滚异常类型 -
timeout
超时时间 -
propagation
事务的传播特性,意思就是事务中调用其他事务方法,则其他事物方法是否归属在现有这个事务.- Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。(
也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务 ) - Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。
- Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
- Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。( 当类A中的 a 方法用默Propagation.REQUIRED模式,类B中的 b方法加上采用Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )
- Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。
- Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。
- Propagation.NESTED :和 Propagation.REQUIRED 效果一样。
- Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。(
(3) @Transactional事务不生效的场景
- @Transactional 应用在非 public 修饰的方法上
- @Transactional 注解属性 propagation 设置错误
- @Transactional 注解属性 rollbackFor 设置错误
- 同一个类中方法调用,导致@Transactional失效
- 异常被你的 catch“吃了”导致@Transactional失效
- 数据库引擎不支持事务
三.多库事务(分布式事务)
1.常用理论
CAP定理
放弃P(CA):如果希望能够避免系统出现分区容错性问题,一种较为简单的做法就是将所有的数据(或者是与事物先相关的数据)都放在一个分布式节点上,这样虽然无法保证100%系统不会出错,但至少不会碰到由于网络分区带来的负面影响
放弃A(CP):其做法是一旦系统遇到网络分区或其他故障时,那受到影响的服务需要等待一定的时间,应用等待期间系统无法对外提供正常的服务,即不可用
放弃C(AP):这里说的放弃一致性,并不是完全不需要数据一致性,是指放弃数据的强一致性,保留数据的最终一致性。
BASE理论
BASE是基本可用,软状态,最终一致性。是对CAP中一致性和可用性权限的结果,是基于CAP定理演化而来的,核心思想是即使无法做到强一致性,但每个应用都可以根据自身的业务特定,采用适当的方式来使系统达到最终一致性
2.分布式事务解决方案
参考文章:
https://blog.csdn.net/john1337/article/details/97551499
https://zhuanlan.zhihu.com/p/100279671
(1) 两阶段提交(2PC)
(2) TCC补偿事务
(3) 基于可靠消息最终一致性事务方案
(4) 最大努力通知事务方案
3.Seata实现分布式事务
官网地址:
http://seata.io/zh-cn/docs/overview/what-is-seata.html
这里我们选用AT模式
(1) 原理分析(AT模式)
因为分布式事务涉及多库操作,单库事务完全不能处理分库事务.其实就涉及到了多种分布式事务方案.但是分布式事务的方案都会有几个基础的组件,其实就是把单库事务放到一个全局的事务中去,由这个全局事务再控制所有的事务统一提交或者统一回滚.
组件:
- 分布式事务Transaction ID XID
全局唯一的事务ID - 全局事务协调Transaction Coordinator(TC)
事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚; - 全局事务的控制者Transaction Manager™
控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议; - 单库事务者Resource Manager(RM)
控制分支事务,负责分支注册,状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚;
架构原理:
(2) 下载安装
1) 官网
下载地址: http://seata.io/zh-cn/
2) 修改配置文件
-
file.conf
修改组名
修改数据库连接
-
registry.conf
注册信息配置
-
db_store.sql
本文件是刚刚我们配置连接的数据库的sql表的建表sql.
所以我们根据杠杆配置的数据库连接,新建一个seata库,并且执行这个sql脚本.
-
db_undo_log.sql
这是日志回滚表的sql脚本,本sql脚本的表需要在所有的微服务连接的数据库中执行,因为这是全局事务回滚的日志表.
3) 启动运行
- 启动nacos
- 启动mysql
- 启动seata
启动成功
(3) 业务微服务中使用seata
- 建module
略 - 改pom
<!--nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<artifactId>seata-all</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>0.9.0</version>
</dependency>
- 写yml
配置seata相关信息
spring:
application:
name: seata-storage-service
cloud:
alibaba:
seata:
#自定义事务组名称需要与seata-server中的对应
tx-service-group: fsp_tx_group
nacos:
discovery:
server-addr: localhost:8848
- 主启动
@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class SeataOrderMainApp2001 {
public static void main(String[] args) {
SpringApplication.run(SeataOrderMainApp2001.class,args);
}
}
- 业务类
利用注解 @GlobalTransactional(name = “order-x001”,rollbackFor = {RuntimeException.class})保证全部微服务的事务要么一起成功,要么一起失败
@Override
@GlobalTransactional(name = "order-x001",rollbackFor = {RuntimeException.class})
public void createOrder(Order order) {
log.info("==========插入订单");
orderDao.create(order);
log.info("==========调用接口,开始缩减库存");
storageService.decreaseStorage(order.getProductId(),order.getCount());
log.info("==========缩减库存结束!");
log.info("==========调用接口,开始缩减账户");
accountService.decreaseAccount(order.getUserId(),order.getMoney());
log.info("==========缩减账户结束!");
log.info("修改当前订单状态");
orderDao.update(order.getUserId(),0);
}
以上就是分布式事务seata的AT模式的代码编写.