1.什么是事务
事务(TRANSACTION)是作为单个逻辑工作单元执行的一系列SQL操作,这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行。
1.1事务的特性
- 原子性:即不可分割性,事务要么全部被执行,要么就全部不被执行
- 一致性或可串性: 事务的执行使得数据库从一种正确状态转换成另一种 正确状态
- 隔离性: 在事务正确提交之前,不允许把该事务对数据的任何改变提供 给任何其他事务
- 持久性: 事务正确提交后,其结果将永久保存在数据库中,即使在事务 提交后有了其它故障,事务的处理结果也会得到保存。
1.2事务并发带来的问题
在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来 完成各自的任务(多个用户对同一 数据进行操作)。并发虽然是必须的, 但可能会导致以下的问题。
- 脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改, 而这种修改还没有提交到数据库中,这时另外一个事务也访问了这 个数 据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另 外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能 是不正确的。
- 丢失修改(Lost to modify): 指在一个事务读取一个数据时,另外一 个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第 二 个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因 此称为丢失修改。 例如:事务1读取某表中的数据A=20,事务2也读 取 A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务 1的修改被丢失。
- 不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。 在这个事务还没有结束时,另一个事务也访问该数据。那么,在第 一个 事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次 读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是 不一样的情况,因此称为不可重复读。
- 幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务 (T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据 时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在 的记录,就好像发生了幻觉一样,所以称为幻读。
不可重复读和幻读区别:
不可重复读的重点是修改,比如多次读取一 条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除比如 多次读取一条记录发现记录 增多或减少了
1.3 我们可以使用事务的隔离级别来解决事务并发带来的问题
- READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交 的数据变更,可能会导致脏读、幻读或不可重复读。
- READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可 以阻止脏读,但是幻读或不可重复读仍有可能发生。
- REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致 的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻 读仍有可能发生。
- SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级 别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就 是说,该级别可以防止脏读、不可重复读以及幻读。
2.分布式事务
分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。以上是百度百科的解释,简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
2.1如何解决分布式问题
使用消息中间件
手写代码解决分布式事务
使用第三方组件--->Seata阿里巴巴的产品
我们这里使用的是Seata来解决分布式问题
3.seata的使用和介绍
3.1介绍seata
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
Seata的执行流程如下:
A服务【订单微服务】的TM[事务发起者]向TC[seata服务端]申请开启一个全局事务,TC就会创建一个全局事务并返回一个唯一的XID
A服务开始远程调用B服务【账户微服务】,此时XID会在微服务的调用链上传播
B服务的RM向TC注册分支事务,并将其纳入XID对应的全局事务的管辖
B服务执行分支事务,向数据库做操作
全局事务调用链处理完毕,TM根据有无异常向TC发起全局事务的提交或者回滚
TC协调其管辖之下的所有分支事务, 决定是否回滚
TM:事务发起者【在哪个方法上添加了全局事务注解的】
TC : 事务管理器【seata的服务端】
RM: 每个操作数据库的微服务
XID: 全局事务id
TM和RM都属于微服务代码
TC: seata服务器。
3.2seata的使用
3.2.1搭建seata服务器
特别注意, SpringCloudAlibaba的版本和Seatad以及其他各版本之间是否是兼容的,不代表最新的版本能在你的项目中.我这里SpringCloud的版本是2.2.3,所以我这里使用的Seata的版本是1.3.0
(1)下载seata1.3.0
https://github.com/seata/seata/releases/tag/v1.3.0https://github.com/seata/seata/releases/tag/v1.3.0记得选择你能用的版本号下载.参照上表.
(2)下载之后将压缩包解压到本地磁盘中.路径最好不要带中文
这里只有三个目录.我们可以从官网提供的源码中把源码中的script中的目录也粘贴到我们的seata中.方便我们之后使用,最后目录结构会变成这样:
(3)修改conf/file.conf
让seata集群信息可以共享,我们应该修改它的保存位置:
注意:mysql5.7以后的版本,driverClassName为"com.mysql.cj.jdbc.Driver",如果是这之前的版本的数据库,不需要修改,默认就为"com.mysql.jdbc.Driver".url这里,我的是mysql8版本以后,url需要加上时区.改完之后保存并退出.
然后将mysql的jar包放入lib目录下.启动的时候需要mysql的jar包,
Seata的lib目录默认是没有mysql的jar包的,但是这个jar包是在lib/jdbc目录下的.因为mysql5.1和mysql8为两个版本,这里Seata两个版本都提供了jar包,按需引入,把需要的jar包复制到lib目录下即可.
需要进入seata/lib/jdbc目录 复制自己对应的mysql版本到上级目录/lib中
(4)创建数据库并导入表结构
我们file.conf文件中已经在其中的db中写了我们使用的数据库名为seata,如果你改成了别的,对应的数据库名也要改.数据库建好以后,从我们导入的script\server\db目录下,找到我们的sql文件.
最后在数据库中就有了这三张表.
再说一遍,源码一定要用对应的版本.在1.5版本,数据库中的表就变成了四张,而我们1.3.0版本只有这三张表.一定要注意!
(5)指定seata的注册中心地址和配置中心的内容
1.
②修改需要放入配置中心的配置内容
- 修改script\config-center目录下的config.txt文件.
3.除此以外,还需要修改组名,为了防止同一服务中心宕机问题
(6)使用nacos/nacos-fonfig.sh 把配置信息放入nacos配置中心
如果你的nacos的配置都是默认的配置(localhost:8848),直接用git方式打开文件直接运行也可以.如果你像我一样修改了nacos的端口号,那么就需要打开git bash 输入以下命令指定nacos的地址和端口号以及namespace(默认用的public如果没用自定的环境就不用配)
sh nacos-config.sh -h localhost -p 8888 -g 分组名 -t 命名空间id
参数说明:
-h:host 默认值为localhost
-p:port 默认值为8848
-g:配置分组,默认为'SEATA_GROUP'
-t:对应nacos的命名空间ID字段,默认值为空
可以看到我们的配置文件都已经导入到nacos配置中心了.
3.2.2配置微服务客户端
(1) 在每个微服务对应的数据库中创建unlog表.如果你就一个数据库,那就引入一个表就行.
这个表不需要手动写,script\client\at\db目录下的mysql.sql文件里面有.
(2) 给需要的微服务(即整个链路上的微服务)都引入seata依赖.这里要注意,你的seata的依赖版本一定要保证是和你用的版本是一致的.就像我用的是1.3.0,项目中引入的依赖也一定要是1.3.0
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
(3) 修改每个微服务的配置文件
(4)在事务的发起者所在微服务上添加分布式事务的注解
@GlobalTransactional
(5)重启相关的微服务.测试事务是否成功.
正常下单:
数据库中的三张表的数据都进行了修改:
(6)现在我们把表恢复成一开始的样子.
然后在下单的业务逻辑中故意加一个异常.如果分布式事务引入成功,那么会显示下单失败,并且三张表的内容都不会修改.
如果没有加分布式事务,我们的数据库中seata-order的表会修改,其他两个表没有变化.如果加了分布式事务,遇到异常会进行事务回滚,我们三张表都不会有改变.
下单提示下单失败:
数据库中:
这样证明我们的分布式事务已经配置并使用成功