## 一、事务的具体定义
事务提供一种机制将一个活动涉及的所有操作纳入到一个不可分割的执行单元,组成事务的所有操作只有在所有操作均能正常执行的情况下方能提交,只要其中任一操作执行失败,都将导致整个事务的回滚。
简单地说,事务提供一种“要么什么都不做,要么做全套(All or Nothing)”机制。ACID就不说了,ACID就是对这句话的一个解释。
## 二、并发环境下的数据库事务
## 2.1 事务并发执行会出现的问题
我们先来看一下事务并发,数据库可能会出现的问题:
* 更新丢失(问题严重)
当有两个并发执行的事务,更新同一行数据,那么有可能一个操作会把另一个操作的更新覆盖掉。
* 脏读 (问题严重)
一个事务读到另一个尚未提交的事务中的数据,即读到了事务的处理过程中的数据,而不是结果数据。 该数据可能会被回滚从而失效。 如果第一个事务拿着失效的数据去处理那就发生错误了。
* 不可重复读 (一般来说可以接受)
不可重复读的含义:一个事务对同一行数据读了两次,却得到了不同的结果。它具体分为如下两种情况:
虚读:在事务1两次读取同一记录的过程中,事务2对该记录进行了修改,从而事务1第二次读到了不一样的记录。
幻读:事务1在两次查询的过程中,事务2对该表进行了插入、删除操作,从而事务1第二次查询的结果数量发生了变化。
> 不可重复读 与 脏读 的区别?
> 脏读读到的是尚未提交的数据,而不可重复读读到的是已经提交的数据,只不过在两次读的过程中数据被另一个事务改过了。
## 2.3 如何解决并发过程中事务问题(事务隔离)
数据库一共有如下四种隔离级别:
* Read uncommitted 读未提交
在该级别下,一个事务对一行数据修改的过程中,不允许另一个事务对该行数据进行修改,但允许另一个事务对该行数据读。
因此本级别下,不会出现更新丢失,但会出现脏读、不可重复读。
* Read committed 读提交 (oracle、sqlserver默认的隔离级别)
在该级别下,未提交的写事务不允许其他事务访问该行,因此**不会出现脏读**;但是读取数据的事务允许其他事务的访问该行数据,因此会出现不可重复读的情况。
* Repeatable read 重复读 (mysql的默认隔离级别)
简单说就是:一个事务开始读或写数据时,不允许其他事务对该数据进行修改。在该级别下,读事务禁止写事务,但允许读事务,因此不会出现同一事务两次读到不同的数据的情况(不可重复读),且写事务禁止其他一切事务。**这个级别无法解决幻读问题**。
* Serializable 序列化
该级别要求所有事务都必须串行执行,因此能避免一切因并发引起的问题,但效率很低。
![](https://box.kancloud.cn/d1f1334fce5a9b832eafb79085333c26_844x334.png)
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed。它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读这些并发问题,应该由应用程序员采用悲观锁或乐观锁来控制。
## 三、事务传播行为
### 举例说明
事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。
用伪代码说明:
~~~
ServiceA {
void methodA() {
ServiceB.methodB();
}
}
ServiceB {
void methodB() {
}
}
~~~
代码中`methodA()`方法嵌套调用了`methodB()`方法,`methodB()`的事务传播行为由`@Transaction(Propagation=XXX)`设置决定。
### Spring中七种事务传播行为
| 事务传播行为类型 | 说明 |
| --- | --- |
| PROPAGATION\_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
| PROPAGATION\_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
| PROPAGATION\_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
| PROPAGATION\_REQUIRES\_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
| PROPAGATION\_NOT\_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
| PROPAGATION\_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
| PROPAGATION\_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION\_REQUIRED类似的操作。 |
定义非常简单,也很好理解,下面我们就进入代码测试部分,验证我们的理解是否正确。
## 四、@Transactional 注解
| 属性名 | 说明 |
| --- | --- |
| value | 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。 |
| propagation | 事务的传播行为,默认值为 REQUIRED。 |
| isolation | 事务的隔离度,默认值采用 DEFAULT。 |
| timeout | 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。 |
| read-only | 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。 |
| rollback-for | 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。 |
| no-rollback- for | 抛出 no-rollback-for 指定的异常类型,不回滚事务。 |
## 五、spring事务的实现
spring事务本质上是依赖于数据库事务
![](https://box.kancloud.cn/83b7902520146de4b8468a6c12210d56_955x538.png)
Spring事务本质上是依赖于第三方的实现
![](https://box.kancloud.cn/312679a3bf90c280b9f4fed1f77a8e37_640x369.png)
## 六、分布式事务
笔者自己将分布式事务分为两种:跨服务的分布式事务,跨库的分布式事务。
1. 跨库的分布式事务:我在做一个服务A的时候,需要同时操作两个数据库。我们之前给大家讲的例子都是这一种,实际上总的思路就是有一个对象统一管理多个事务的提交与回滚。这种分布式事务还是在数据库层面去解决的。
> 为了大家方便理解:我以小故事给大家简单讲一下两段式提交:假如我在外地出差到了妇女节分别用给老婆和妈邮寄了礼物,我希望他们两个都收到礼物并拥有礼物。首先我用快递把礼物邮到家里,这是一段提交。老婆和妈告诉我:收到了收到了,谢谢!才发现包装盒带密码,她们没法看礼物。然后我给老婆和妈打电话,告诉他们密码,他们就可以看了。大家有问题:1.一阶段没问题有响应才到第二阶段,二阶段礼物到家之后,老婆电话停机怎么办?只给妈妈打了电话,没给老婆打。笔者说:这种问题是所有分布式事务解决方案都要面对的问题(面对网络与宕机问题任何分布式事务都会失效),这个不是两段式提交自己的问题。那么就没办法了么?有,网络超时就有异常,有异常就回滚,告诉妈妈这个礼物有问题要退回。2.给妈打完电话之后我的电话停机了怎么办?就是补偿方案,我记得这个电话没打给老婆,等电话充费后再打,进而达到最终一致性,重点是我要记得。在这个例子中,我就是一个事务管理器,而老婆和妈就是资源管理器,资源管理器是在数据库的组件,而事务管理器通常是应用组件。而不同的数据库对两段式提交的支持是不同的,也就是资源管理器不同。参考:[分布式事务之——MySQL对XA事务的支持](https://blog.csdn.net/l1028386804/article/details/79769043)
![](https://box.kancloud.cn/6658fe83ef6c710cfe52f0d43c6b408e_1009x561.png)
2. 跨服务分布式事务: 也就是说我在做一个服务A的时候,需要通过网络调用多个其他服务,有可能第一个服务B成功了,第二个服务C执行失败了。这种分布式单纯的依靠数据库层面就很难解决了,一般都是通过最终一致性的方式解决。比如:通过MQ,给服务B发消息,服务B执行,然后真的做持久化操作数据入库了。给服务C发消息,服务C执行失败,这个消息就会存在MQ里面,依照一定的策略还会发给服务C,直到服务C成功为止。这样保障最终一致。
- 内容简介
- 第一章 Spring boot 简介
- 1.1 helloworld
- 1.2 提高开发效率工具lombok
- 1.3 IDEA热部署
- 1.4 IDEA常用插件
- 1.5 常用注解
- 第二章 RESTful接口
- 2.1 RESTful风格API
- 2.1.1 spring常用注解开发RESTful接口
- 2.1.2 HTTP协议与Spring参数接收注解
- 2.1.3 Spring请求处理流程注解
- 2.2 JSON数据格式处理
- 2.2.1 Jackson的转换示例代码
- 2.3 针对接口编写测试代码
- 2.3.1 编码接口测试示例代码
- 2.3.2 带severlet容器的接口测试示例代码
- 2.3.3 Mockito测试示例代码
- 2.3.4 Mockito轻量测试
- 2.4 使用swagger2构建API文档
- 2.4.1 swagger2示例代码
- 2.4.2 pom.xml
- 2.5 使用swagger2导出各种格式的接口文档
- 第三章 sping boot配置管理
- 3.1 YAML语法
- 3.2 YAML绑定配置变量的方式
- 3.3 YAML配置属性值校验
- 3.4 YAML加载外部配置文件
- 3.5 SpEL表达式绑定配置项
- 3.6 不同环境下的多配置
- 3.7 配置文件的优先级
- 3.8 配置文件敏感字段加密
- 第四章 连接数据库使用到的框架
- 4.1 spring JDBC
- 4.2 mybatis配置mybatisgenerator自动生成代码
- 4.3 mybatis操作数据库+dozer整合Bean自动加载
- 4.4 spring boot mybatis 规范
- 4.5 spirng 事务与分布式事务
- 4.6 spring mybaits 多数据源(未在git版本中实现)
- 4.7 mybatis+atomikos实现分布式事务(未在git版本中实现)
- 4.8 mybatis踩坑之逆向工程导致的服务无法启动
- 4.9 Mybatis Plus
- 4.9.1.CURD快速入门
- 4.9.2.条件构造器使用与总结
- 4.9.3.自定义SQL
- 4.9.4.表格分页与下拉分页查询
- 4.9.5.ActiveRecord模式
- 4.9.6.主键生成策略
- 4.9.7.MybatisPlus代码生成器
- 4.9.8.逻辑删除
- 4.9.9.字段自动填充
- 4.9.10.多租户解决方案
- 4.9.11.雪花算法与精度丢失
- 第五章 页面展现整合
- 5.1 webjars与静态资源
- 5.2 模板引擎与未来趋势
- 5.3 整合JSP
- 5.4 整合Freemarker
- 5.5 整合Thymeleaf
- 5.6 Thymeleaf基础语法
- 5.7 Thymeleaf内置对象与工具类
- 5.8 Thymeleaf公共片段(标签)和内联JS
- 第六章 生命周期内的拦截、监听
- 6.1 servlet与filter与listener的实现
- 6.1.1 FilterRegistration
- 6.1.2 CustomFilter
- 6.1.3 Customlister
- 6.1.4 FirstServlet
- 6.2 spring拦截器及请求链路说明
- 6.2.1 MyWebMvcConfigurer
- 6.2.2 CustomHandlerInterceptor
- 6.3 自定义事件的发布与监听
- 6.4 应用启动的监听
- 第七章 嵌入式容器的配置与应用
- 7.1 嵌入式的容器配置与调整
- 7.2 切换到jetty&undertow容器
- 7.3 打war包部署到外置tomcat容器
- 第八章 统一全局异常处理
- 8.1 设计一个优秀的异常处理机制
- 8.2 自定义异常和相关数据结构
- 8.3 全局异常处理ExceptionHandler
- 8.3.1 HelloController
- 8.4 服务端数据校验与全局异常处理
- 8.5 AOP实现完美异常处理方案
- 第九章 日志框架与全局日志管理
- 9.1 日志框架的简介与选型
- 9.2 logback日志框架整合使用
- 9.3 log4j2日志框架整合与使用
- 9.4 拦截器实现用户统一访问日志
- 第十章 异步任务与定时任务
- 10.1 实现Async异步任务
- 10.2 为异步任务规划线程池
- 10.3 通过@Scheduled实现定时任务
- 10.4 quartz简单定时任务(内存持久化)
- 10.5 quartz动态定时任务(数据库持久化)
- 番外章节
- 1.windows下安装git
- 1 git的使用
- 2 idea通过git上传代码到github
- 2.maven配置
- 3.idea几个辅助插件
- 4.idea配置数据库
- 5.搭建外网穿透实现外网访问内网项目
- 6.idea设置修改页面自动刷新
- 7.本地tomcat启动乱码
- 8.win10桌面整理,得到一个整洁的桌面
- 9.//TODO的用法
- 10.navicat for mysql 工具激活
- 11.安装redis
- 12.idea修改内存
- 13.IDEA svn配置
- 14.IntelliJ IDEA像Eclipse一样打开多个项目