2023-03:事务相关

主要着重下面几点进行介绍

  1. 各种读:脏读、幻读、不可重复读
  2. ACID
  3. 事务隔离级别
  4. spring 事务传播机制
  5. spring事务失效原因
  6. spring事务实现方式:编程式、声明式(@Transactional)

读取数据存在的问题

  1. 脏读,可以读取到未提交的事务中的数据(此时事务未进行隔离,read uncommit)
  2. 不可重复读,读取到的数据,在其他事务的影响下可能会在多次读的情况下数据不一致(事务虽然进行了隔离,但是没对数据进行缓存,导致在读取过程中数据可能变化,read commit)
  3. 幻读,读取到的数据,与持久化的数据不一致,常见的是,可重复读启用了数据缓存,导致了数据副本与持久化的数据不一致,可使用间隙锁(?)解决
  4. serializable,通过加锁机制,保证了数据不会在读取的过程中被修改,避免了幻读的情况发生,效率很低,因为在锁的获取时需要等待,产生额外的开销

ACID

  1. 原子性:事务中的操作要么一起成功,要么一起(事务是最小的逻辑单元)
  2. 一致性:事务之前是一个一致性,之后也是一个一致性,前后都符合约束条件,比如总金额不变、一个人账户不能出现负数
  3. 隔离性:事务在提交之前,是不可被影响的,被隔离出来
  4. 持久化:事务在提交之后,会持久化到数据库中,永久保存
  5. 原子性、隔离性、持久性,实现了一致性,是事务的最终目的

事务隔离级别

  1. read uncommit,读未提交,可以看到其他事务中未提交的事务数据,会出现脏读
  2. read commit,在同一个事务中,同一个数据可能存在重复读的时候数据不一致,也就是不可重复读,会出现不可重复读
  3. repatable read,可重复读,在同一事务中,同一个数据,重复读,不会出现不一致情况,可重复读,但是相应的会出现幻读的情况,因为我们是可重复读,必定会导致这个情况发生(存在缓存,读取的数据被修改了,但是缓存没变,此时发生幻读)
  4. serialable,序列化,每一次开启事务,都会对行进行加锁,这样子保证了不会出现幻读,因为这条数据不可被再次修改、select count(*) from t_table,这时锁的是表
  5. spring中还多了一个数据库默认级别(跟数据库有关,mysql可重复读、oracle读提交),spring进行配置选择隔离级别,如果数据库不支持,则使用数据库默认级别

spring的事务传播机制(Propagation):A事务中存在B事务,A -> B,此时B事务上需要确定事务的传播机制

  1. required,默认传播机制:当前有事务,则加入;无事务,则创建新的
  2. required_new,无论事务是否存在,都创建一个新的事务
  3. nested(嵌套),有事务,则嵌套事务中执行;无事务,开启新的事务(required)(嵌套事务,会在主事务上增加一个savepoint,当嵌套事务出现异常,回滚到savepoint,不影响主事务)(需要设置nestedTransactionAllowed=true)
  4. supports,有事务,就加入;无事务,就不创建事务
  5. mandatory(强制性的),有事务,则加入;无事务,抛出异常(也就是说,必须加入当前事务)
  6. not_supported,以非事务方式执行,如果当前存在事务,则挂起当前事务(这里出现异常,不会影响到外面的回滚)
  7. never,不使用事务,如果当前事务存在,则抛出异常
事务传播机制分类:
-当前存在事务
	-加入事务:required、nested(嵌套加入)、supports、mandatory
	-创建新的事务:required_new
	-不加入事务:not_supported
	-抛出异常:never
-当前无事务
	-创建新事务:required、required_new、nested
	-不创建事务:never、not_supported、supports
	-抛出异常:mandatory

事务失效原因:参考文章

  1. 从代理角度来看
    1. 未配置默认Service回滚,且未使用@Transactional修饰
    2. 方法的修饰符不是public类型的,另外final与static也是不支持事务的(无法生成cglib的代理,因为cglib的本质是继承)
    3. 事务方法所处的对象并没有Spring管理,自然也不会生成代理对象(对象不是代理生成,不具备事务回滚的功能)。
    4. 调用本地方法也就是this,而这个this不是代理类
  2. 从异常角度来看
    1. 事务方法中手动捕获异常,但并未向上抛出,那么该方法不会进行回滚。
    2. 事务方法中抛出的异常,不在rollbackFor中,也不属于RuntimeException或者Error时,不会进行回滚。spring默认只对RuntimeException异常回滚
  3. 从传播行为角度来看
    1. 传播行为指定错误,例如:内部方法和外部方法不在一个事务中,内部方法回滚后,外部方法没有进行回滚
      1. required,如果事务B加入事务A,那么try-catch事务B,事务A依旧会回滚
      2. required_new,如果事务B加入事务A,那么try-catch事务B,事务A不会回滚
      3. nested,如果事务B加入事务A,那么try-catch事务B,事务A会回滚到savepoint,相当于不回滚(所以有人说nested=required_new,但是如果外部事务A发生异常,那么事务B也跟着回滚,此时因为是同一个事务,相当于required,总结来说还是有区别的,区别点在于savepoint的存在)
  4. 从方法调用角度来看
    1. 非事务方法调用事务方法,因为相当于调用原始类的普通方法。(虽然事务B加上了@Transactional,但是A方法未添加这个注解)
    2. 事务方法a在另外一个线程中调用事务方法b,a出现异常,不会回滚b。因为每个线程处于不同的事务中,不会互相影响
  5. 从外部条件角度来看
    1. 存储引擎不支持事务,例如表的类型为MyIASM,本身就不支持事务
  6. 补充说明:
    1. spring使用cglib生成被代理的对象,spring bean产生的对象是ehancer.create()生成的,事实的确如此,idea debug,然后查看service的类属性(注意哈,service需要@Transactional修饰

spring事务实现方式:编程式、声明式

声明式:@Transactional,生成相应的代理对象,设置自动提交为false
编程式:private TransactionTemplate transactionTemplate;
posted @ 2023-08-31 17:14  season-qd  阅读(5)  评论(0编辑  收藏  举报