2023-03:事务相关
主要着重下面几点进行介绍
- 各种读:脏读、幻读、不可重复读
- ACID
- 事务隔离级别
- spring 事务传播机制
- spring事务失效原因
- spring事务实现方式:编程式、声明式(@Transactional)
读取数据存在的问题
- 脏读,可以读取到未提交的事务中的数据(此时事务未进行隔离,read uncommit)
- 不可重复读,读取到的数据,在其他事务的影响下可能会在多次读的情况下数据不一致(事务虽然进行了隔离,但是没对数据进行缓存,导致在读取过程中数据可能变化,read commit)
- 幻读,读取到的数据,与持久化的数据不一致,常见的是,可重复读启用了数据缓存,导致了数据副本与持久化的数据不一致,可使用间隙锁(?)解决
- serializable,通过加锁机制,保证了数据不会在读取的过程中被修改,避免了幻读的情况发生,效率很低,因为在锁的获取时需要等待,产生额外的开销
ACID
- 原子性:事务中的操作要么一起成功,要么一起(事务是最小的逻辑单元)
- 一致性:事务之前是一个一致性,之后也是一个一致性,前后都符合约束条件,比如总金额不变、一个人账户不能出现负数
- 隔离性:事务在提交之前,是不可被影响的,被隔离出来
- 持久化:事务在提交之后,会持久化到数据库中,永久保存
- 原子性、隔离性、持久性,实现了一致性,是事务的最终目的
事务隔离级别
- read uncommit,读未提交,可以看到其他事务中未提交的事务数据,会出现脏读
- read commit,在同一个事务中,同一个数据可能存在重复读的时候数据不一致,也就是不可重复读,会出现不可重复读
- repatable read,可重复读,在同一事务中,同一个数据,重复读,不会出现不一致情况,可重复读,但是相应的会出现幻读的情况,因为我们是可重复读,必定会导致这个情况发生(存在缓存,读取的数据被修改了,但是缓存没变,此时发生幻读)
- serialable,序列化,每一次开启事务,都会对行进行加锁,这样子保证了不会出现幻读,因为这条数据不可被再次修改、select count(*) from t_table,这时锁的是表
- spring中还多了一个数据库默认级别(跟数据库有关,mysql可重复读、oracle读提交),spring进行配置选择隔离级别,如果数据库不支持,则使用数据库默认级别
spring的事务传播机制(Propagation):A事务中存在B事务,A -> B,此时B事务上需要确定事务的传播机制
- required,默认传播机制:当前有事务,则加入;无事务,则创建新的
- required_new,无论事务是否存在,都创建一个新的事务
- nested(嵌套),有事务,则嵌套事务中执行;无事务,开启新的事务(required)(嵌套事务,会在主事务上增加一个savepoint,当嵌套事务出现异常,回滚到savepoint,不影响主事务)(需要设置nestedTransactionAllowed=true)
- supports,有事务,就加入;无事务,就不创建事务
- mandatory(强制性的),有事务,则加入;无事务,抛出异常(也就是说,必须加入当前事务)
- not_supported,以非事务方式执行,如果当前存在事务,则挂起当前事务(这里出现异常,不会影响到外面的回滚)
- never,不使用事务,如果当前事务存在,则抛出异常
事务传播机制分类:
-当前存在事务
-加入事务:required、nested(嵌套加入)、supports、mandatory
-创建新的事务:required_new
-不加入事务:not_supported
-抛出异常:never
-当前无事务
-创建新事务:required、required_new、nested
-不创建事务:never、not_supported、supports
-抛出异常:mandatory
事务失效原因:参考文章
- 从代理角度来看
- 未配置默认Service回滚,且未使用@Transactional修饰
- 方法的修饰符不是public类型的,另外final与static也是不支持事务的(无法生成cglib的代理,因为cglib的本质是继承)
- 事务方法所处的对象并没有Spring管理,自然也不会生成代理对象(对象不是代理生成,不具备事务回滚的功能)。
- 调用本地方法也就是this,而这个this不是代理类
- 从异常角度来看
- 事务方法中手动捕获异常,但并未向上抛出,那么该方法不会进行回滚。
- 事务方法中抛出的异常,不在rollbackFor中,也不属于RuntimeException或者Error时,不会进行回滚。spring默认只对RuntimeException异常回滚
- 从传播行为角度来看
- 传播行为指定错误,例如:内部方法和外部方法不在一个事务中,内部方法回滚后,外部方法没有进行回滚
- required,如果事务B加入事务A,那么try-catch事务B,事务A依旧会回滚
- required_new,如果事务B加入事务A,那么try-catch事务B,事务A不会回滚
- nested,如果事务B加入事务A,那么try-catch事务B,事务A会回滚到savepoint,相当于不回滚(所以有人说nested=required_new,但是如果外部事务A发生异常,那么事务B也跟着回滚,此时因为是同一个事务,相当于required,总结来说还是有区别的,区别点在于savepoint的存在)
- 传播行为指定错误,例如:内部方法和外部方法不在一个事务中,内部方法回滚后,外部方法没有进行回滚
- 从方法调用角度来看
- 非事务方法调用事务方法,因为相当于调用原始类的普通方法。(虽然事务B加上了@Transactional,但是A方法未添加这个注解)
- 事务方法a在另外一个线程中调用事务方法b,a出现异常,不会回滚b。因为每个线程处于不同的事务中,不会互相影响
- 从外部条件角度来看
- 存储引擎不支持事务,例如表的类型为MyIASM,本身就不支持事务
- 补充说明:
- spring使用cglib生成被代理的对象,spring bean产生的对象是ehancer.create()生成的,事实的确如此,idea debug,然后查看service的类属性(注意哈,service需要@Transactional修饰
spring事务实现方式:编程式、声明式
声明式:@Transactional,生成相应的代理对象,设置自动提交为false
编程式:private TransactionTemplate transactionTemplate;
梦想不多,口袋有糖,卡里有钱,未来有你