事务

手动事务

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

丢失更新:

两个事务同时更新一行数据,最后一个事务的更新会覆盖掉第一个事务的更新,从而导致第一个事务更新的数据丢失,后果比较严重。一般是由于没加锁的原因造成的。

脏读(Dirty reads):

一个事务A读取到了另一个事务B还没有提交的数据,并在此基础上进行操作。如果B事务rollback,那么A事务所读取到的数据就是不正确的,会带来问题。

不可重复读(Non-repeatable reads):

在同一事务范围内读取两次相同的数据,所返回的结果不同。比如事务B第一次读数据后,事务A更新数据并commit,那么事务B第二次读取的数据就与第一次是不一样的。

幻读(Phantom reads):

一个事务A读取到了另一个事务B新提交的数据。比如,事务A对一个表中所有行的数据按照某规则进行修改(整表操作),同时,事务B向表中插入了一行原始数据,那么后面事务A再对表进行操作时,会发现表中居然还有一行数据没有被修改,就像发生了幻觉,飘飘欲仙一样。

注意:

不可重复读和幻读的区别是,不可重复读对应的表的操作是更改(UPDATE),而幻读对应的表的操作是插入(INSERT),两种的应对策略不一样。对于不可重复读,只需要采用行级锁防止该记录被更新即可,而对于幻读必须加个表级锁,防止在表中插入数据。

为了处理这几种问题,SQL定义了下面的4个等级的事务隔离级别:

未提交读(READ UNCOMMITTED ):

最低隔离级别,一个事务能读取到别的事务未提交的更新数据,很不安全,可能出现丢失更新、脏读、不可重复读、幻读;

提交读(READ COMMITTED):

一个事务能读取到别的事务提交的更新数据,不能看到未提交的更新数据,不会出现丢失更新、脏读,但可能出现不可重复读、幻读;

可重复读(REPEATABLE READ):

保证同一事务中先后执行的多次查询将返回同一结果,不受其他事务影响,不可能出现丢失更新、脏读、不可重复读,但可能出现幻读;

序列化(SERIALIZABLE):

最高隔离级别,不允许事务并发执行,而必须串行化执行,最安全,不可能出现更新、脏读、不可重复读、幻读,但是效率最低。
隔离级别越高,数据库事务并发执行性能越差,能处理的操作越少。所以一般地,推荐使用REPEATABLE READ级别保证数据的读一致性。对于幻读的问题,可以通过加锁来防止。
MySQL支持这四种事务等级,默认事务隔离级别是REPEATABLE READ。Oracle数据库支持READ COMMITTED 和 SERIALIZABLE这两种事务隔离级别,所以Oracle数据库不支持脏读。Oracle数据库默认的事务隔离级别是READ COMMITTED。

https://www.cnblogs.com/snsdzjlz320/p/5761387.html
https://www.cnblogs.com/chengshun/p/9778880.html

隔离级别测试

插入事务A(remark字段从0到10)

@Override
@Transactional
public void insertA() {
    MessageSystem byId = this.getById(1372785969765355522L);
    for (int i = 1; i <0 10; i++) {
        byId.setRemark(i + "");
        this.updateById(byId);
    }
}

插入事务B(remark字段从0到20)

@Override
public void insertB() {
    MessageSystem byId = this.getById(1372785969765355522L);
    for (int i = 1; i <= 20; i++) {
        byId.setRemark(i + "");
        this.updateById(byId);
    }
}

说明

  1. 采用getByIdNoCache方法,为了避免mybatis一级缓存对结果造成影响
@Select("select * from pe_message_system where id = #{id} and #{uuid} = #{uuid}")
MessageSystem getByIdNoCache(@Param("id") long id,@Param("uuid") String uuid);
  1. @Transactional(timeout = 30)
    当到达超时时间后,再执行数据库操作,会报错
org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Tue Apr 13 12:16:58 CST 2021
	at org.springframework.transaction.support.ResourceHolderSupport.checkTransactionTimeout(ResourceHolderSupport.java:155)
	at org.springframework.transaction.support.ResourceHolderSupport.getTimeToLiveInMillis(ResourceHolderSupport.java:144)
	at org.springframework.transaction.support.ResourceHolderSupport.getTimeToLiveInSeconds(ResourceHolderSupport.java:128)
	at org.mybatis.spring.transaction.SpringManagedTransaction.getTimeout(SpringManagedTransaction.java:125)
	at com.baomidou.mybatisplus.core.executor.MybatisSimpleExecutor.prepareStatement(MybatisSimpleExecutor.java:92)
	...

  1. 未提交读

    @Override
    @Transactional(isolation = Isolation.READ_UNCOMMITTED, timeout = 30)
    public void unCommitRead() {
        // 未提交读
        for (int i = 1; i <= 3; i++) {
            // 打断点阻塞,等待线程插入事务A执行后,再继续查询
            MessageSystem byIdNoCache = baseMapper.getByIdNoCache(1372785969765355522L, UuidUtils.generateUuid());
            System.out.println(byIdNoCache.getRemark());
        }
    }
    

    结果: [1,2,3]
    结论:查询到了其他事务未提交的数据

  2. 提交读

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public void commitRead() {
        // 提交读
        // 第一次从DB查询,默认为0
        MessageSystem byIdNoCache = baseMapper.getByIdNoCache(1372785969765355522L, UuidUtils.generateUuid());
        String A = byIdNoCache.getRemark(); // 0
        // 第二次等待insertA执行后再查询
        byIdNoCache = baseMapper.getByIdNoCache(1372785969765355522L, UuidUtils.generateUuid());
        String B = byIdNoCache.getRemark(); // 10
        // 第三次等待insertB执行后再查询
        byIdNoCache = baseMapper.getByIdNoCache(1372785969765355522L, UuidUtils.generateUuid());
        String C = byIdNoCache.getRemark();// 20
    }
    

    结果:[0,10,20]
    结论:查询到了其他事务已提交的数据

  3. 可重复读

    @Override
    @Transactional(isolation = Isolation.REPEATABLE_READ)
     public void repeatableRead() {
         // 可重复读
         // 第一次从DB查询,默认为0
         MessageSystem byIdNoCache = baseMapper.getByIdNoCache(1372785969765355522L, UuidUtils.generateUuid());
         String A = byIdNoCache.getRemark(); // 0
         // 第二次等待insertA执行后再查询
         byIdNoCache = baseMapper.getByIdNoCache(1372785969765355522L, UuidUtils.generateUuid());
         String B = byIdNoCache.getRemark(); // 0
     }
    

    结果:[0,0]
    结论:在同一事务,多次查询的结果相同,不被其他事务结果影响
    mysql默认事务隔离级别为可重复读

传播机制(基于默认Required)

  1. a()不加事务,调用this.child()方法,child()有事务,异常抛在child()
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    插入两条数据,child事务未生效

  1. a()加事务,调用this.child()方法,child()有事务,异常抛在child()
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    未插入数据,回滚,事务生效

  1. a()加事务,调用this.child()方法,child()无事务,异常抛在child()
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    未插入数据,回滚,事务生效

  1. a()加事务,调用this.child()方法,child()无事务,异常抛在child(),a()捕获异常不抛出
    在这里插入图片描述
    在这里插入图片描述
    插入两条数据,异常未抛出事务不生效,数据同时commit,不会出现child()数据先提交的情况

  1. a()加事务,调用this.child()方法,child()有、无事务,异常抛在a()
    在这里插入图片描述
    在这里插入图片描述
    未插入数据,回滚,事务生效
    1-5总结:
    事务只在最外层有用,内部不管加不加事务都是失效的。最外层如果加了事务,内层都会受到事务的影响,如果外层事务没有回滚,内层也不会回滚;同理,外层回滚,内层也回滚。
    原因:因为spring事务注解是基于代理类,如果调用第一个方法没有创建事务代理类,那么内部调用类中的其他方法,也只是基于原始的对象来调用,不会调用代理类的方法,所以事务无效。

  1. A.b()加事务,调用B.child()方法,child()加事务,异常抛在child()
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    未插入数据,回滚,事务生效

  1. A.b()加事务,调用B.child()方法,child()不加事务,异常抛在child()
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    未插入数据,回滚,事务生效

  1. A.b()加事务,调用B.child()方法,child()加事务,异常抛在b()
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    未插入数据,回滚,事务生效

  1. A.b()加事务,调用B.child()方法,child()不加事务,异常抛在b()
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    未插入数据,回滚,事务生效

  1. A.b()不加事务,调用B.child()方法,child()加事务,异常抛在b()
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    插入一条数据,child事务生效
    6-10总结:
    A如果加了事务,内部B调用都会受到事务的影响,A回滚B也回滚。A如果没有事务,那么B的事务会生效,与本类方法调用不同

其他传播机制

https://zhuanlan.zhihu.com/p/148504094

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值