事务隔离级别和Spring事务传播

事务     

   数据库的事务,及事务的4种特性:原子性、一致性、持久性、隔离性 这里就不多解释了。主要记录下 事务的隔离性。

        事务的隔离 ,我的理解是,表示 有多个事务在操作DB 时的一种 读原则吧。如果有其他事务正在操作DB时,我这个事务读取DB时的值是什么样的。

以mysql为例,事务的隔离有 串行、重复读、读已提交、读未提交;

  • 串行: 非常好理解,就是 提交的事务会排队执行。
  • 读已提交:T1会读取T2已提交的
  • 读未提交:T1会读取T2还没有提交的
  • 重复读:T1 读取的是 T1开始时 DB中的值,不管T2是提交了还是没有提交,都不影响。

常用的事务相关命令

-- 查看事务隔离级别
select @@global.tx_isolation

select @@session.tx_isolation  -- 当前会话


-- 修改事务隔离级别
set session transaction isolation level read uncommitted

set session transaction isolation level read committed

set session transaction isolation level repeatable read

set session transaction isolation level serializable 


-- 查看自动提交
show variables like 'autocommit'


-- 开启自动提交
set autocommit = 1

-- 关闭自动提交
set autocommit = 0  -- 和  start transaction 类似

-- 事务就绪,执行第一个select语句时,才真正开始事务
start transaction

-- 事务立即开启
start transaction with consistent snapshot

-- 提交事务
commit

-- 回滚事务
ROLLBACK

开启事务:

第一种:设置后,只有执行了第一个 select 语句,事务才真正开启。

-- 关闭自动提交
set autocommit = 0

-- 显示开启
start transaction

第二种:设置后,事务立即开启

-- 显示开启
start transaction with consistent snapshot

一般,在编程过程中,都是使用 第一种  set autocommit = 0 的方式 ,关闭 事务的自动提交 来开启事务的,也就是 需要执行第一个 select 语句,当前的事务才真正的开启。如果使用的是mysql的默认的可重复读的隔离级别,当前事务如果还没有执行select语句,那么其他事务的一些commit之后的更新,当前事务依然可以看的到。

示例:可重复读、自动提交 验证 事务开启时机

示例1:

示例2:

通过示例1和示例2,可以说明,一个事务真正开启是在 执行了 select 之后,才会记录当前的快照。

事务隔离级别的行为控制,是针对当前事务 对 数据 读取的行为 控制

session A  设置了 读已提交,表示 session A 的事务 对 已提交的数据更改可读

session B 设置了 可重复读,表示 session B 的事务 对 数据是可重复读取,不论该数据是否被修改

session B 的修改,如果提交了,session A 就可以读到数据,

        因为session A是 读(任何事务)已提交(的修改)

session A的修改,不论是否提交,session B 都读取不到

        因为session B 是可重复读 当前事务内的数据,而不受其他事务对该数据的修改

Spring事务传播

        MySql中的事务,是数据库层面支持的。在编程过程中,不同的业务代码 如何 操作事务?

        事务 一般是指  一些列的 SQL 语句,事务的原子性要求 事务中的 所有SQL语句,要么全部成功,要不全部失败。 这一特性,如何在编程的时候实现呢?

        在编程时,不同的SQL语句 很可能是遍布在不同的service中的,如果把这些SQL语句控制在同一个事务中呢?这就是 Spring的事务传播机制的用处

        举一个场景 来理解一下 Spring的事务传播机制能给我们编程带来哪些好处。

        比如一段业务逻辑:

                执行一条 更新语句

                其他业务代码

                 调用一个service方法,该service方法需要执行其他的更新语句

                其他业务代码

                调用另一个service方法:

                        该service方法需要执行其他的更新语句

                        调用另一个service方法,该service方法执行一些增删改查,远程调用等操作

                其他业务代码

                结束

要求:上述的业务逻辑处理过程中,有任何一个SQL执行异常或者代码抛异常,那么其他所有已执行的SQL需要全部回滚。 这时,如果没有spring的事务穿模,是不是有点不好办了?也许,在没有Spring框架时,有其他的办法来解决。不过,就目前Spring的项目来说,只有事务传播机制能帮我们来解决这个问题。

spring 事务传播(不同service 方法之间的调用):

第一类:严格在事务中运行

第二类:严格不在事务中运行

第三类:不严格 在或者不在 事务中运行,不自己新建事务

第一类:严格在事务中运行

1. 严格在事务中运行, 存在,则沿用,不存在,则新建  REQUIRED 必须的 

3. 严格在事务中运行,存在,则沿用,不存在,则抛异常  MANDATORY 强制的

4.严格在事务中运行,存在,则挂起当前事务再新建事务,不存在,则新建事务 REQUIRED_NEW

7.严格在事务中运行,存在,则在当前事务中嵌套一个事务,不存在,则新建 NESTED 嵌套

第三类:不严格 在或者不在 事务中运行,不自己新建事务

2. 不严格在事务中运行,存在,则沿用,不存在,则 不新建  SUPPORTS 支持

第二类:严格不在事务中运行

5. 严格不在事务中运行,存在,则挂起当前事务,不存在,则不新建  NOT_SUPPORTED

6.严格不在事务中运行,存在,则抛异常,不存在,则不新建 NEVER 绝不

名词解释:

挂起事务: 类似于  把当前连接保存,使用 新的连接来处理后续的db操作

嵌套事务:使用 savepoint 机制

一般,常用的就是 REQUIRED 和 REQUIRED_NEW。

如果希望 运行在已有事务中,则使用 REQUIRED

如果希望 运行在 自己的 事务中,则使用 REQUIRED_NEW

Required,受外围方法的事务情况影响

required_new 不受外围方法的事务影响

默认传播方式:REQUIRED

这里写图片描述

spring boot 事务示例

使用注解:

@Transactional

该注解有几个属性值:

  • 事务管理器
  • 事务传播方式
  • 事务隔离级别
  • 回滚方式
  • 不回滚方式

如果一个方法,没有加@Transactional注解,就没有使用 Spring 的事务管理功能。此时的数据库SQL操作,可以看成是用 控制台在执行SQL语句,即:SQL操作自动提交

事务传播规律:

在一个方法上加上事务注解:

        方法内的多个SQL操作,在同一个事务内

        方法内调用其他service的方法 或者 通过依赖自己 调用自己的方法:

                其他service方法加事务注解:根据传播方式使用事务

                其他service方法没加事务注解:和调用方法在同一个事务内

        方法内调用自己的方法:

                不论自己方法上有没有添加事务注解,都是和调用方法在同一个事务内   

在一个方法上不加事务注解:

       其他SQL或者其他service方法的事务,都跟当前方法没有关系

事务失效的几种情况

1. 方法不是public

2. 类内的方法互相调用

        解决:

                1. 将方法拆分到不同的service

                 2. 自己注入自己

                 3.  <aop:aspectj-autoproxy expose-proxy="true"/>或者 <aop:config expose-proxy="true">

                代码调用:

                        ((ServiceA ) AopContext.currentProxy()).insert()

3. 数据源没有配置事务管理器

4. 异常没cache住或者抛出的异常类型不回滚事务,那么事务不会回滚

                

并发操作的事务示例

需求:对库存表进行更新操作,如果库存不为0,则库存减1

第一种:

查询之后,产品剩余数量可能被其他线程更新了,所以 用查询出来的结果判断,会不准确

public void reduceProduct(){

        // 查询
        ZProduct zProduct = zProductMapper.selectById(Integer.valueOf(1));
        // 判断
        if (zProduct.getProductCount() > 0){
            // 更新库存
            ZProduct updateZProduct = new ZProduct();
            updateZProduct.setId(zProduct.getId());
            updateZProduct.setProductCount(zProduct.getProductCount() - 1);
            zProductMapper.updateById(updateZProduct);

            // 生成订单
            zOrderMapper.insert(new ZOrder("book"));
        }
    }

第二种:

这种写法也存在问题。

原因是:

第一个线程更新的事务,在释放锁之后,才提交。而在释放锁和事务提交之间,其他线程的事务看到的还是之前的库存值,因此,其他线程再进行 更新库存时,用的是旧数据进行更新,导致库存少减一次。

如果使用下面的语句更新库存,其实也存在问题,因为虽然库存值最终被更新为0,但是订单已经生成了啊

@Update("update z_product set product_count = product_count - 1 where id = ${id}")
int updateProduct(@Param("id") Integer id);

示例代码

 ReentrantLock lock = new ReentrantLock();

    @Transactional
    public void reduceProduct(){
        lock.lock();
        System.out.println("lock");

        try {
            ZProduct zProduct = zProductMapper.selectById(Integer.valueOf(1));
            System.out.println("count:" + zProduct.getProductCount());

            if (zProduct.getProductCount() > 0){

                ZProduct updateZProduct = new ZProduct();
                updateZProduct.setId(zProduct.getId());
                updateZProduct.setProductCount(zProduct.getProductCount() - 1);
                zProductMapper.updateById(updateZProduct);

                zOrderMapper.insert(new ZOrder("book"));
                System.out.println("success");
            }else {
                System.out.println("没库存");
            }

            System.out.println("unlock");
        }finally {

            lock.unlock();
        }
    }

第三种:

对于第二种的问题处理,只需要将 业务的事务和 锁分开,满足 事务提交后,锁再释放就OK 了

---------------------------------------------------底线---------------------------------------------------------------

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值