数据库事务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.传播特性
2.Spring声明式事务原理
@Transactional PlatformTransactionManager •TransactionInterceptor
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属性,不然将回滚失效。
catch异常无法回滚: 只有抛出非检查异常或是rollbackFor中指定的异常才能触发回滚。如果我们把异常catch住,而且没抛出,则会导致无法触发回滚,这也是开发中常犯的错误。例如:
@Transactional public void insert(List<User> users) { try{ //jdbcTemplate.insert(...) } catch (Exception e) { e.printStackTrace(); } }
这里由于catch住了所有Exception,并且没抛出。当插入发生异常时,将不会触发回滚。但同时我们也可以利用这种机制,用try-catch包裹不用参与事务的数据操作,例如对于写入一些不重要的日志,我们可将其用try-catch包裹,避免抛出异常,则能避免写日志失败而影响事务的提交。