本地事务与分布式事务
本地事务
事务的基本性质
事务的概念:事务是逻辑上一组操作,组成这组操作各个逻辑单元,要么一起成功,要么一起失败。
数据库事务的几个特性:原子性(Atomicity)、一致性( Consistency )、隔离性或独立性( lsolation)和持久性(Durabilily),简称就是ACID;
-
原子性:一系列的操作整体不可拆分,要么同时成功,要么同时失败
-
一 致性:数据在事务的前后,业务整体一致。
- 转账。A:1000; B:1000; 转200 事务成功; A: 800 B: 1200
-
隔离性:事务之间互相隔离。
-
持久性:一旦事务成功,数据一定会落盘在数据库。
在以往的单体应用中,我们多个业务操作使用同一条连接操作不同的数据表,一旦有异常,我们可以很容易的整体回滚;
Business:我们具体的业分代码
Storage:库存业务代码;扣库存
Order:订单业务代码;保存订单
Account:账号业务代码;减账户余额
比如买东西业务,扣库存,下订单,账户扣款,是一个整体;必须同时成功或者失败一个事务开始,代表以下的所有操作都在同一个连接里面;
事务的隔离级别
概念
- READ UNCOMMITTED (读未提交)
该隔离级别的事务会读到其它未提交事务的数据,此现象也称之为脏读。 - READ COMMITTED (读提交)
一个事务可以读取另一个已提交的事务,多次读取会造成不一样的结果,此现象称为不可重复读问题,Oracle 和SQL Server的默认隔离级别。 - REPEATABLE READ (可重复读)
该隔离级别是MvSQL默认的隔离级别,在同一个事务里,select的结果是事务开始时时间点的转态,因此,同样的select 操作读到的结果会是一致的, 但是,会有幻读现象。MySQL的InnoDB 引擎可以通过next-key locks 机制(参考下文"行锁的算法"一节)来避免幻读。 - SERIALIZABLE (序列化)
在该隔离级别下事务都是串行顺序执行的,MySQL数据库的InnoDB引擎会给读操作隐式加一把读共享锁,从而避免了脏读、不可重读复读和幻读问题。
事务并发引起一些读的问题:
概念 | 解释 |
---|---|
脏读 | 一个事务可以读取另一个事务未提交的数据 |
不可重复读 | 一个事务可以读取另一个事务已提交的数据 单条记录前后不匹配 |
虚读(幻读) | 一个事务可以读取另一个事务已提交的数据 读取的数据前后多了点或者少了点 |
并发写:使用mysql默认的锁机制(独占锁)
解决读问题:设置事务隔离级别,隔离级别越高,性能越低。
一般情况下:脏读是不可允许的,不可重复读和幻读是可以被适当允许的。
基本命令
命令 | 意思 |
---|---|
SELECT @@global.tx_isolation | 查看全局事务隔离级别 |
set global transaction isolation level read committed; | 设置全局事务隔离级别 |
SELECT @@tx_isolation | 查看当前会话事务隔离级别 |
set session transaction isolation level read committed | 设置当前会话事务隔离级别 |
select @@autocommit | 查看mysql默认自动提交状态 |
set autocommit = 0; | 设置mysql默认自动提交状态【0不自动提交】 |
start transaction; | 开启一个事务 |
commit | 提交事务 |
rollback | 回滚事务 |
savepoint tx1 | 在事务中创建一个保存点 |
rollback to tx1 | 回滚到保存点 |
事务的传播行为
- PROPAGATION REQUIRED: 如果当前没有事务,就创建一个新事务, 如果当前存在事务,就加入该事务,该设置是最常用的设置。
- PROPAGATION SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
- PROPAGATION _MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
- PROPAGATION REQUIRES NEW:创建新事务,无论当前存不存在事务,都创建新事务。
- PROPAGATION NOT SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
- PROPAGATION _NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION REQUIRED 类似的操作。
最常用的就是:PROPAGATION REQUIRED、PROPAGATION REQUIRES NEW
SpringBoot 事务关键点
事务的自动配置
TransactionAutoConfiguration
事务的坑
在同一个类里面,编写两个方法,内部调用的时候,会导致事务设置失效。原因是绕过了动态代理对象,事务使用代理对象来控制的。
解决:使用代理对象来调用事务方法
1、引入spring-boot-starter-aop;这个场景启动器引入了aspectjweaver
2、@EnableAspectJAutoProxy(exposeProxy = true);开启aspectj 动态代理功能。以后所有的动态代理都是aspectj创建的(特点:即使没有接口也可以创建动态代理对象;exposeProxy = true 对外暴露代理对象)
3、本类方法互调,获取到本代理对象的方法(通过AopContext获取当前类的代理对象)
OrderServiceImpl orderService = (OrderServiceImpl) AopContext.currentProxy();
orderService.b();
// 同一个对象内事务方法互调失效。原因:绕过了动态代理对象
// 事务使用代理对象来控制的
@Transactional(timeout = 30) // a 事务的所有设置会传播到和他公用一个事务的方法
public void a() {
// b,c做任何设置都没用。都是和a同一个事务
// this.b(); 没用
// this.c(); 没用
OrderServiceImpl orderService = (OrderServiceImpl) AopContext.currentProxy();
orderService.b();
orderService.c();
// bService.b(); // a 事务
// cService.c(); // 新事物(不回滚)
int i = 1 / 0;
}
@Transactional(propagation = Propagation.REQUIRED, timeout = 2) // 设置的timeout不管用,因为你使用的事务是a的
public void b() {
}
@Transactional(propagation = Propagation.REQUIRES_NEW, timeout = 20) // 管用
public void c() {
}
分布式事务
为什么有分布式事务
- 分布式系统经常出现的异常
- 机器宕机、网络异常、消息丢失、消息乱序、数据错误、不可靠的TCP、存储数据丢失…
- 通过异常机制解决多个系统数据库一致性的问题有缺陷。假如其他系统是成功执行的,但是由于业务超时,我们这边认为对方出现异常,本系统的业务就回滚了,但是别的系统没有回滚。(假异常)
- 调用顺序的问题,本系统调用第三方系统成功,我们也感知到成功了,但是我们下面的代码出错了。我们这边能回滚,但是第三方数据已经修改了。(调用多个系统的数据库操作,第一个调用成功,第二个调用失败)