Spring事务

数据库事务ACID

  1. A (Atomicity) 原子性: 原子性很容易理解,也就是说事务里的所有操作要么全部做完,要么都
    不做,事务成功的条件是事务里的所有操作都成功,只要有一个操作失败,整个事务就失败,需要回滚。
  2. C (Consistency) 一致性: 一致性也比较容易理解,也就是说数据库要一直处于一致的状态,事务的运行不会改变数据库原本的一致性约束。
  3. I (Isolation) 隔离性: 所谓的独立性是指并发的事务之间不会互相影响,如果一个事务要访问的数据正在被另外一个事务修改,只要另外一个事务未提交,它所访问的数据就不受未提交事务的影响。
  4. D (Durability) 持久性: 持久性是指一旦事务提交后,它所做的修改将会永久的保存在数据库上,即使出现宕机也不会丢失。
  5. 数据库ACID如何保证: 以mysql的InnoDB存储引擎为例,事务的隔离性是通过数据库锁的机制实现的,持久性通过redo log(重做日志)来实现,原子性和一致性通过Undo log来实现。

事务并发带来的问题?

  1. 脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么
    另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
  2. 丢失修改(Lost to modify): 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务1读取某表中的数据A=10,事务2也读取A=10,事务1修改A=A-2,事务2也修改A=A-2,最终结果A=8,事务1的修改被丢失。
  3. 不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两
    次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
  4. 幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
  5. 不可重复读的重点是修改,幻读的重点在于新增或者删除。

数据库事务的隔离级别

  1. READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  2. READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  3. REPEATABLE-READ(可重读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  4. SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
  5. MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE- READ(可重读)。

Spring事务

  1. 传播特性

    image-20210601221103964

  2. Spring声明式事务原理

    @Transactional
    PlatformTransactionManagerTransactionInterceptor
    
  3. Spring事务问题

    1. 非public方法失效: @Transactional只有标注在public级别的方法上才能生效, 对于非public方法将不会生效。这是由于Spring AOP不支持对private、protect方法进行拦截。从原理上来说,动态代理是通过接口实现,所以自然不能支持private和protect 方法的。而CGLIB是通过继承实现,其实是可以支持protect方法的拦截的,但Spring AOP中并不支持这样使用,猜测是出于代理方法应是public的考虑,以及为了保持CGLIB和动态代理的一致。

    2. 自调用失效: 当通过在Bean的内部方法直接调用带有@Transactional的方法时,@Transactional将失效,例如:

      public void saveAB(A a, B b){
      	saveA(a);
      	saveB(b);
      }
      @Transactional
      public void saveA(A a){
      	dao.saveA(a);
      }
      @Transactional
      public void saveB(B b){
      	dao.saveB(b);
      }
      
      
    3. 检查异常默认不回滚:

      1. 在默认情况下,抛出非检查异常会触发回滚,而检查异常不会。
      2. 只有RuntimeException和Error的实例,即非检查异常,或者在@Transaction中通过rollbackFor属性指定的回滚异常类型,才会回滚事务。否则将继续提交事务。所以如果需要对非检查异常进行回滚,需要记得指定rollbackFor属性,不然将回滚失效。
    4. catch异常无法回滚: 只有抛出非检查异常或是rollbackFor中指定的异常才能触发回滚。如果我们把异常catch住,而且没抛出,则会导致无法触发回滚,这也是开发中常犯的错误。例如:

      @Transactional 
      public void insert(List<User> users) { 
          try{
              //jdbcTemplate.insert(...)
          } catch (Exception e) { 
              e.printStackTrace(); 
          } 
      } 
      

      这里由于catch住了所有Exception,并且没抛出。当插入发生异常时,将不会触发回滚。但同时我们也可以利用这种机制,用try-catch包裹不用参与事务的数据操作,例如对于写入一些不重要的日志,我们可将其用try-catch包裹,避免抛出异常,则能避免写日志失败而影响事务的提交。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

隐 风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值